Using Django+ Vue.js Develop personal blog website (full version with source code) - Python course design final project

Keywords: Django Mobile Redis Session

Page display:

Source code

home page

Different plates

Registration page

Login page

personal information

Write article page

Article details page

Article review

This article focuses on the Python back-end part. Since only using vue as the js framework is not a front-end separation project, the front-end is not introduced separately.

1, Project content (what to do)

This project implements a blog system on the web side, which allows multiple people to register and log in, and users can publish blogs on the website and browse the blogs published by others.

The practical significance is: when a group or a class needs to learn and communicate, it can be used. Everyone can share their learning experience on it, and then learn from each other. Because the project is divided into several parts, it is very convenient to find the relevant technology stack.

Main modules of the project

Demand driven development, first analyze the demand (here refer to the demand of other blog systems on the Internet):

Main functions of the project:

  1. As long as the user has a mobile number, they can register and log in. When registering, they need picture verification code and SMS verification code;
  2. Users can choose to remember me after logging in, so that they can automatically log in even if the browser is closed next time;
  3. Users can modify their personal information, including uploading their avatars, etc;
  4. Blogs are classified by type. Administrators can modify the specific categories on the background management page;
  5. Users can write blog online, text editor adopts rich text editor, users can write HTML code and store it in database by using graphical interface;
  6. Users can view the blogs written by everyone, and comment on them at the same time. The display of blogs and comments use the paging function;
  7. The number of times users view blogs is recorded as the number of views, and the number of views is recorded as the number of comments. According to these two systems, articles with high popularity are automatically recommended.

2, Project plan (how to do it)

Project development mode

  • Development mode: front end and back end are not separated;
  • Back end framework: Django + Django template engine;

Language and tool version

  • Python 3.6
  • MySQL 5.7
  • Django 3.0
  • Redis 3.2

Database design

There are four main entities involved:

  • Article: user: comment: Classification

Their relationship is as follows:

  • An article corresponds to a classification, and a classification can have multiple articles, so the relationship between them is many to one;
  • One user can publish multiple articles and multiple comments at the same time, and each comment only corresponds to one user, and each article only belongs to one user, so there is one to many between users, articles and comments;

With entities and relationships, the following is represented by ER diagram:

Add attributes (incomplete attributes, which will be supplemented in the conceptual model):

Then design its conceptual model:

The corresponding physical model is:

In the physical model, due to the one to many relationship, two foreign key constraints are added to the article table and comment table.

Create data table

Generally, we can directly create databases and tables here, but because Django is used, it integrates ORM framework, namely Object Relation Mapping, so we don't need to write SQL statements directly, but can write entity classes, and then execute Django's file migration command to automatically generate data tables.

Let's first look at the User entity:

class User(AbstractUser):
    # Mobile number (length 11, unique, not empty)
    mobile = models.CharField(max_length=11, unique=True, blank=False)

    # avatar information (picture type, saved in avatar folder under the project directory_ Distinguished by creating folders by date, can be blank)
    avatar = models.ImageField(upload_to='avatar/%Y%m%d/', blank=True)

    # Profile information (maximum length is 500, can be empty)
    user_desc = models.CharField(max_length=500, blank=True)

Three fields are defined here. Why?

Because we have integrated an entity class AbstractUser that comes with Django, which provides us with general information such as user name, name, email address, etc., we do not need to specify it separately.

But to state in the configuration file that you have changed the user class:

# Replace the user model of the system with our customized user model
AUTH_USER_MODEL = 'users.User'

Automatically generated users table:

Article classification entity:

class ArticleCategory(models.Model):
    """
    //Article classification entity class
    """
    # Column title
    title = models.CharField(max_length=100, blank=True)
    # Creation time
    created = models.DateTimeField(default=timezone.now)

Article entity class:

class Article(models.Model):
    """
    //Article entity class
    """
    # Foreign key constraint: associated with user table: set cascading deletion: delete the user and delete all articles of the user at the same time
    author = models.ForeignKey(User, on_delete=models.CASCADE)

    # Article title map: picture type, saved to the article folder under the project directory_ Distinguished by creating folders by date, can be blank
    avatar = models.ImageField(upload_to='article/%Y%m%d/', blank=True)

    # Article column is a "one to many" foreign key: one column corresponds to multiple articles
    category = models.ForeignKey(
        ArticleCategory,
        null=True,
        blank=True,
        on_delete=models.CASCADE,
        related_name='article'
    )

    # Article tag
    tags = models.CharField(max_length=20, blank=True)

    # Article title
    title = models.CharField(max_length=100, null=False, blank=False)

    # outline
    summary = models.CharField(max_length=200, null=False, blank=False)

    # Text
    content = models.TextField()

    # Views: positive integer
    total_view = models.PositiveIntegerField(default=0)

    # Number of article comments
    comments_count = models.PositiveIntegerField(default=0)

    # Article creation time: write current time by default
    created = models.DateTimeField(default=timezone.now)

    # Article update time: automatically write the current time
    updated = models.DateTimeField(auto_now=True)

Comment entity class:

class Comment(models.Model):
    """
    //Comment entity class
    """
    # Comments
    content = models.TextField()
    # Articles reviewed
    article = models.ForeignKey(Article, on_delete=models.SET_NULL, null=True)
    # Commenting users
    user = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True)
    # Comment time
    created = models.DateTimeField(auto_now_add=True)

Project structure and routing settings

The project structure is as follows:

  • home as sub app: manage blogs and comments
  • libs is a dependent third-party library: picture verification code and mobile phone number SMS verification code;
  • logs not delivered: used for log output;
  • media resource file: head picture and so on;
  • my_blog main application: used to register other applications, set super administrator, etc;
  • Static static resource directory: js, css, etc;
  • Template template engine folder: several main pages;
  • User subapplication: it is used to realize user login and other functions;
  • utils tool class package: Custom loading code information;

The routing structure is as follows:

Root route:

urlpatterns = [
    path('admin/', admin.site.urls),

    # include first set a tuple (routing of sub application, name of sub application)
    # Then set the namespace namespace to prevent the problem of duplicate routing names in different applications
    path('', include(('users.urls', 'users'), namespace='users')),
    path('', include(('home.urls', 'home'), namespace='home'))
]

users sub route:

urlpatterns = [

    # Path (route, view function name)
    path('register/', RegisterView.as_view(), name='register'),

    # Route of picture verification code
    path('imagecode/', CaptchaView.as_view(), name='imagecode'),

    # Route of mobile phone verification code
    path('smscode/', SmsCodeView.as_view(), name='smscode'),

    # Route of login
    path('login/', LoginView.as_view(), name='login'),

    # Log out
    path('logout/', LogoutView.as_view(), name='logout'),

    # Forget password
    path('forgetpassword/', ForgetPasswordView.as_view(), name='forgetpassword'),

    # User profile page
    path('center/', UserCenterView.as_view(), name='center'),

    # Route for writing articles
    path('writeblog/', WriteBlogView.as_view(), name='writeblog'),
]

home sub route:

urlpatterns = [
    # The route of the home page means that if you don't write any path, you will jump to the home page
    path('', IndexView.as_view(), name='index'),
    path('detail/', DetailView.as_view(), name='detail'),
]

Here, the route namespace is used to set the route namespace, mainly for the convenience of later reference.

For example, this is often written on the page:

3, Technical points (Key Technologies)

  1. Using Vue as the front frame;
  2. Django is used as the back-end framework;
  3. Using Django template engine;
  4. Send by SMS of cloud communication;
  5. Using session technology;

The registration part is too complicated. I don't need SMS verification code for small websites. The main reason I do this is to learn to use SMS verification code for authentication, because I didn't use SMS verification code before development, and I used email verification code. This time, I will be familiar with similar projects after I use it in this project Yes.

Log management

It is worth mentioning that the logging function of Django is used. The configuration in the settings file is as follows:

# Set up log
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,  # Disable existing loggers or not
    'formatters': {  # Format of log information display
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s'
        },
        'simple': {
            'format': '%(levelname)s %(module)s %(lineno)d %(message)s'
        },
    },
    'filters': {  # Filtering logs
        'require_debug_true': {  # django only outputs logs in debug mode
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {  # Log processing method
        'console': {  # Output log to terminal
            'level': 'INFO',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        'file': {  # Output the log to the file. The log will be saved to the logs folder of the project directory in the form of a file;
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': os.path.join(BASE_DIR, 'logs/blog.log'),  # Location of log files
            'maxBytes': 300 * 1024 * 1024,
            'backupCount': 10,
            'formatter': 'verbose'
        },
    },
    'loggers': {  # Loggers
        'django': {  # A logger named django is defined
            'handlers': ['console', 'file'],  # You can output logs to both the terminal and the file at the same time
            'propagate': True,  # Continue to deliver log information
            'level': 'INFO',  # The minimum log level received by the logger is INFO
        },
    }
}

Then you can log/ blog.log View the log information under the file:

Manage the background through Django's own background management system:

Create user

Create superuser

17858918831
wangshuo
wangsuoo@qq.com
wsuo2821


Article classification



Copy more data through worms:

INSERT INTO tb_article(avatar,tags,title,summary,content,total_view,comments_count,created,updated,author_id,category_id)
SELECT avatar,tags,title,summary,content,total_view,comments_count,created,updated,author_id,category_id FROM tb_article;

4, Difficulties and solutions (all problems encountered, causes and solutions)

1. Picture verification code

Here, a third-party library is used to realize the function. The tool address is: https://bitbucket.org/akorn/wheezy.captcha

The front-end user can click to switch the verification code. The strategy used here is to write an interface to return the verification code picture. In order to realize the function of timed expiration, I saved it in Redis, so that the verification code will expire after the specified time:

"""
//Generate the verification code and store it in Redis
:param request: Request object
:return: Return value,Here is a response object
"""
# First get the uuid of the verification code from the front end
uuid = request.GET.get('uuid')
# Then make a judgment to ensure the UUID exists
if uuid is None:
    return HttpResponseBadRequest("Wrong verification code!")
# Generate verification code through verification code library
text, image = captcha.generate_captcha()
# Get Redis connection
redis_conn = get_redis_connection("default")
# Stored in Redis, (key, survival time, value) 300s = 5 minutes
redis_conn.setex("img:%s" % uuid, 300, text)
# Return to the front-end verification code picture
return HttpResponse(image, content_type="image/jpeg")

The UUID of the verification code is stored in Redis as the key, and the value of the real verification code is stored as the value, so that it can be directly compared during the later verification. If it is matched, it can be registered, and if it is not matched, an error is reported.

As shown in the figure, the values stored in Redis:

2. SMS verification code


Here is the website of Ronglian cloud communication: https://www.yuntongxun.com/


After registration, you can set your phone number as the test account, and only send SMS to your phone number. The successful test page is as follows:


It uses the officially provided interface, which can be used by filling in your own key information in the project file:

You can use the main function to test:

Interface design:

"""
    Use Random library to generate Random mobile phone verification code, and then store it in Redis. At the same time, print it out on the console for debugging
    Finally, the interface is called "Rong Lian Yun" to send the verification code.
        Note that at present, the verification code can only be sent to my designated mobile number. 17858918830
"""
#Random generation of 6-bit verification code
sms_code = '%06d' % randint(0, 999999)
#Output the verification code to the console
logging.info(sms_code)
#Save to Redis
redis_conn.setex('sms:%s' % mobile, 300, sms_code)
#Send SMS 17858918830;
#Parameter 1 is mobile number;
#Parameter 2 is the content in the template, your verification code is 1234, please fill in within 5 minutes;
#Parameter 3 is short message template, which can only be 1
CCP().send_template_sms(mobile, [sms_code, 5], 1)
#Response results
return JsonResponse({'code': RETCODE.OK,
                     'errmsg': 'send SMS successfully'})

The strategy adopted here is to use random library to generate a 6-bit random number, then save the verification code to Redis, and set the expiration time. In order to facilitate debugging, it is also printed to the console. In the actual test, my mobile phone can receive the verification code, and the registration is successful, that is, the official free interface response is relatively slow.

3. Log in status hold

State hold:

  • Write the unique identification information (such as user ID) of the authenticated user into the current session;

  • Django user authentication system provides login() method to encapsulate the operation of writing session, which helps us to maintain state quickly;

  • login() location: django.contrib.auth H. init__ . py file.

At the same time, if the user clicks the remember me button, the login information will be written into the cookie.

# Authentication login user: authentication is the authentication method of Django
user = authenticate(mobile=mobile, password=password)
if user is None:
    return HttpResponseBadRequest('Wrong user name or password')
# Maintain login status
login(request, user)
# Jump the page according to the next parameter: the parameter passed by the user information page
nex = request.GET.get('next')
if nex:
    response = redirect(nex)
else:
    response = redirect(reverse('home:index'))
# Store in Cookie
if remember != 'on':
    # The user didn't choose to remember me. The browsing area will expire when the session ends
    request.session.set_expiry(0)
    response.set_cookie('is_login', True)
    response.set_cookie('username', user.username, max_age=30 * 24 * 3600)
else:
    # Users choose to remember me: set_expiry expires in two weeks by default
    request.session.set_expiry(None)
    response.set_cookie('is_login', True, max_age=14 * 24 * 3600)
    response.set_cookie('username', user.username, max_age=30 * 24 * 3600)

In addition, because the name of Redis is configured as session, it will be automatically stored in Redis for one month. That is to say, you do not need to log in again within one month after the browser is closed.

"session": {  # session
    "BACKEND": "django_redis.cache.RedisCache",
    "LOCATION": "redis://127.0.0.1:6379/1",
    "OPTIONS": {
        "CLIENT_CLASS": "django_redis.client.DefaultClient",
    }
},


# Change session access from database storage to Redis storage
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "session"

I tested it two days ago, and now Redis also includes:

To view the Cookie information used:

# The default authentication method is to authenticate username. We need to change the authentication field to mobile.
# So we need to modify it in the User's model
USERNAME_FIELD = 'mobile'


You can see that the phone number has been stored in the cookie as username

4. Log out

Use template language:

<a class="dropdown-item" href='{% url "user:logout" %}'>Sign out</a>

Login is displayed:

At this time, click exit to log in and find that there is no more:

5. Picture upload

Pictures belong to static resources. Before uploading pictures, I'll talk about how to access them. I created a static folder in the root directory, and then configured the following information in settings:

# Routes to static resources
STATIC_URL = '/static/'

# Set the load path of local static resources
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static')
]

After the image upload is encapsulated by Django, it becomes very simple. When I define the user entity, I specify the upload folder:

# avatar information (picture type, saved in avatar folder under the project directory_ Distinguished by creating folders by date, can be blank)
avatar = models.ImageField(upload_to='avatar/%Y%m%d/', blank=True)

But this is not the only way. You need to specify the access path and access route.

# Set image upload path
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')

# Unified routing of pictures
MEDIA_URL = '/media/'
# Set picture routing access rules
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

After modification:

6. Cross domain problem

There is nothing wrong, but I can't access it. The error information is as follows:

Seeing CSRF brings up the cross domain problem, because similar situations have been encountered in the previous Web development, which can be solved in the front-end or the back-end. Django provides a simpler method:

Just add a label directly under the form.

7. User access issues

Classes that come with Django:

Let our class implement this class:

Then start:

The error report cannot be found because the default jump connection of Django is accounts, which needs to be modified in the settings:

# Set the route for users not logged in to jump
LOGIN_URL = '/login/'


😝 Source address: https://gitee.com/wsuo/Blog_Django Find a Star ⭐.

Getting started with Django: building a popular blog system

Posted by plzhelpme on Sun, 14 Jun 2020 01:53:43 -0700