1. E-commerce mode
B2B: Business to Business (Alibaba)
C2C: customer to consumer
B2C: Business to Customer
C2B: Customer to Business individual to enterprise (commodity house matching) (individual demand, enterprise production)
O2O: Online to Offline (meituan, hungry)
F2C: Factory to Customer
B2B2C: Enterprise (Supplier) - enterprise (e-commerce enterprise) - individual (consumer) (Jingdong, tmall)
2. Page description
index.html Home page (with commodity classification menu, display of "register | login" and "user information" switch, carousel chart)
list.html Product list page
detail.html Product details page
cart.html Shopping cart page
place_order.html Submit order page
login.html Landing page
register.html Registration page
user_center_info.html User center - user information page
user_center_order.html User center - user order page
user_center_site.html User center - receiving address page
3. SPU and SKU
SPU=Standard Product Unit, which is the smallest unit of product information aggregation. For example, iPhone 7 is an SPU. No matter in black, gold, 64G or 128G, it has a product profile, product promotion video and other information. It is an abstract concept and cannot correspond to a specific product;
SKU=Stock Keeping Unit, which is the smallest inventory unit that cannot be separated physically. After the classification of types and specifications, for example, iPhone 7 rose gold 128G is SKU, which can truly correspond to a specific product;
4. Database design
Note: for a one to many relationship, multiple entries cannot be placed in the same table, and multiple entries should be extracted to create a single table;
User table:
ID, user name, password, mailbox, activation or not, permission ID (ordinary user / administrator)
Address table:
ID, user ID, recipient, address, zip code, contact information, default or not
Product SKU table:
ID, SKU name, introduction, price, unit, inventory, sales volume, picture (one), status (on and off shelves), category ID, SPU ID
(goods and pictures are one-to-many relationships, so pictures should be separated from goods as a new table, but in order to display the goods list faster, we should put a picture for the list into the goods list, because if we don't put it, there will be 50 associated queries to display 50 goods in the list, and so many associated queries affect efficiency, so we exchange space for space Take time)
Product SPU table:
ID, SPU name, details
(in fact, the category ID also belongs to the SPU table, because the gold and black of iPhone 7 belong to the same category, but for the previous reason, if you put it in the SPU table, you need to use a lot of associated queries on the product list page, which is inefficient.)
Product picture table:
ID, picture, SKU commodity ID
List of commodity categories:
ID, category name, logo, picture
Home page rotation table:
ID, SKU ID, picture, index (sequence)
Homepage promotion activity table:
ID, picture, active page url, index
Display table of classified commodities on the home page:
ID, SKU ID, category ID, index, display ID (picture / text)
Order information form:
ID, receiving address ID, user ID, payment method, total amount, freight, payment status, creation time
Order line item table:
ID, SKU ID, order ID, product quantity, price when placing order, comment
redis realizes the functions of shopping cart and browsing record;
5. Build Django framework
Premise: install django, pymysql, mysql and other packages;
Find a suitable path to generate Django project and application:
Open with PyCharm, create a new python package called apps in the project directory, and include all applications:
6. Registered application (scheme 2 is not easy to use after testing, only scheme 1 can be used)
1) Scheme 1: add one layer of apps to the path/
Because apps / are built by yourself, you can't find where to write userDjango directly. To write apps/user:
# settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'apps.cart', #Shopping Cart module 'apps.goods', # Commodity module 'apps.order', # Order module 'apps.user', # User module ]
# urls.py from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('user/', include('apps.user.urls')), ]
2) Scheme 2: add apps / to BASE_DIR
Because Django looks for registered applications in the path, just like the environment variable of the computer, BASE_DIR in path (BASE_DIR is the absolute path of the project), so he will look for base_ The child directory of dir will not be found (i.e. apps can be found, apps/user can not be found). Therefore, as long as we add apps to the path, we can find the user under apps:
# settings.py Add # Add apps / to path import sys sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
# settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'cart', #Shopping Cart module 'goods', # Commodity module 'order', # Order module 'user', # User module ]
# urls.py from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('user/', include('user.urls', namespace='user')), # User module; namespace is used for reverse parsing to generate url path('order/', include('order.urls', namespace='order')), # Order module path('cart/', include('cart.urls', namespace='cart')), # Shopping Cart module path('', include('goods.urls', namespace='goods')), # Product module, as the default home page, matches unconditionally, so it is placed at the bottom ]
The code reports yellow because PyCharm can't find the path, because the code hasn't been run, apps / hasn't been added to the path, and there will be no problem after running:
sys.path and os.path The former is the environment variable, the latter is the object to operate the path, which can be used for path join and other operations;
7. Template path
Create the templates folder in the root directory of the project and add this path:
8. Configuration database
# settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'dailyfresh', 'USER': 'root', 'PASSWORD': 'root', 'HOST': 'localhost', # Write localhost directly with mysql of this machine 'PORT': 3306, } }
# __init__.py import pymysql # Cheat Django to avoid version checking and error reporting pymysql.version_info = (1, 3, 13, 'final', 0) pymysql.install_as_MySQLdb()
To create a database manually:
desc tablename; // View table structure
Note: if the mysql bound by the project is on another host of the same subnet, the mysql of the other host cannot be bound to 127.0.0.1. To bind the IP that other hosts can access, but mysql is bound to 127.0.0.1 by default. Modify:
sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf //Modify bind address= xx.xx.xx . XX (IP found by ifconfig) sudo service mysql restart
In addition, the host of mysql also authorizes the host of the project to enter the mysql client and execute the following commands:
// Authorize all tables of the dailyfresh database to the root user of the IP to log in with the password root grant all privileges on dailyfresh.* to 'root'@'Host of the project IP' identified by 'root' with grant option; // Make the authorization effective flush privileges;
9. Configure language time zone
10. Static file directory
Create the static folder in the root directory of the project settings.py Add:
# settings.py Add # Static file directory STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
11. Application url
One for each application urls.py , temporarily:
from django.urls import path urlpatterns = [ ]
Django's url system is actually routing;
12. Design model class
The new python package in the project directory is called db, including a python file called base_model.py :
The contents are as follows:
from django.db import models class BaseModel(models.Model): '''Model abstract base class''' create_time = models.DateTimeField(auto_now_add=True, verbose_name='Creation time') update_time = models.DateTimeField(auto_now=True, verbose_name='Update time') is_delete = models.BooleanField(default=False, verbose_name='Delete tag') class Meta: # Description is an abstract model class abstract = True # Meta is a nested class. The purpose is to add some functions to the parent class or specify some standards # Because the model classes to be written next have some common properties, write these properties to a class, and let those model classes inherit this class
Next in the application models.py Write in model class:
user/models.py:
from django.db import models from django.contrib.auth.models import AbstractUser from db.base_model import BaseModel class User(AbstractUser, BaseModel): '''User model class''' class Meta: db_table = 'df_user' verbose_name = 'user' verbose_name_plural = verbose_name class Address(BaseModel): '''Address model class''' user = models.ForeignKey('User', verbose_name='Account', on_delete=models.CASCADE) receiver = models.CharField(max_length=20, verbose_name='addressee') addr = models.CharField(max_length=256, verbose_name='Receiving address') zip_code = models.CharField(max_length=6, null=True, verbose_name='Postal Code') phone = models.CharField(max_length=11, verbose_name='contact number') is_default = models.BooleanField(default=False, verbose_name='Default or not') class Meta: db_table = 'df_address' verbose_name = 'address' verbose_name_plural = verbose_name # The User model class does not write too many fields, but inherits the abstract class AbstractUser built in Django # AbstractUser class is used for authentication by Django's built-in user authentication system, # Instead of writing user authentication code, we use Django's built-in authentication system, # AbstractUser class contains user name, password, mailbox, activation no, administrator no and other fields, # After we inherit it, we don't have to write these fields
goods/models.py:
from django.db import models from db.base_model import BaseModel from tinymce.models import HTMLField class GoodsType(BaseModel): '''Commodity type model class''' name = models.CharField(max_length=20, verbose_name='Category name') logo = models.CharField(max_length=20, verbose_name='identification') image = models.ImageField(upload_to='type', verbose_name='Product type picture') class Meta: db_table = 'df_goods_type' verbose_name = 'Commodity category' verbose_name_plural = verbose_name def __str__(self): return self.name class GoodsSKU(BaseModel): '''commodity SKU Model class''' status_choices = ( (0, 'Offline'), (1, 'go online'), ) type = models.ForeignKey('GoodsType', verbose_name='Commodity category', on_delete=models.CASCADE) goods = models.ForeignKey('Goods', verbose_name='commodity SPU', on_delete=models.CASCADE) name = models.CharField(max_length=20, verbose_name='Commodity name') desc = models.CharField(max_length=256, verbose_name='Product introduction') price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='commodity price') unite = models.CharField(max_length=20, verbose_name='Commodity unit') image = models.ImageField(upload_to='goods', verbose_name='Product picture') stock = models.IntegerField(default=1, verbose_name='Merchandise inventory') sales = models.IntegerField(default=0, verbose_name='Sales volume of goods') status = models.SmallIntegerField(default=1, choices=status_choices, verbose_name='Commodity status') class Meta: db_table = 'df_goods_sku' verbose_name = 'commodity' verbose_name_plural = verbose_name class Goods(BaseModel): '''commodity SPU Model class''' name = models.CharField(max_length=20, verbose_name='commodity SPU name') # Rich text type: text with formatting detail = HTMLField(blank=True, verbose_name='Product details') class Meta: db_table = 'df_goods' verbose_name = 'commodity SPU' verbose_name_plural = verbose_name class GoodsImage(BaseModel): '''Product image model class''' sku = models.ForeignKey('GoodsSKU', verbose_name='commodity', on_delete=models.CASCADE) image = models.ImageField(upload_to='goods', verbose_name='Picture path') class Meta: db_table = 'df_goods_image' verbose_name = 'Product picture' verbose_name_plural = verbose_name class IndexGoodsBanner(BaseModel): '''Home page carousel commodity display model class''' sku = models.ForeignKey('GoodsSKU', verbose_name='commodity', on_delete=models.CASCADE) image = models.ImageField(upload_to='banner', verbose_name='picture') index = models.SmallIntegerField(default=0, verbose_name='Order of presentation') class Meta: db_table = 'df_index_banner' verbose_name = 'Home page carousel' verbose_name_plural = verbose_name class IndexTypeGoodsBanner(BaseModel): '''Home category commodity display model''' DISPLAY_TYPE_CHOICES = ( (0, "title"), (1, "picture") ) type = models.ForeignKey('GoodsType', verbose_name='Commodity type', on_delete=models.CASCADE) sku = models.ForeignKey('GoodsSKU', verbose_name='commodity SKU', on_delete=models.CASCADE) display_type = models.SmallIntegerField(default=1, choices=DISPLAY_TYPE_CHOICES, verbose_name='Display type') index = models.SmallIntegerField(default=0, verbose_name='Order of presentation') class Meta: db_table = 'df_index_type_goods' verbose_name = "Home page category display goods" verbose_name_plural = verbose_name class IndexPromotionBanner(BaseModel): '''Homepage promotion model class''' name = models.CharField(max_length=20, verbose_name='Activity name') url = models.URLField(verbose_name='Activity links') image = models.ImageField(upload_to='banner', verbose_name='Activity picture') index = models.SmallIntegerField(default=0, verbose_name='Order of presentation') class Meta: db_table = 'df_index_promotion' verbose_name = "Home promotion" verbose_name_plural = verbose_name # HTMLField is not a built-in type of Django, but a rich text type introduced by itself, # The difference between rich text and ordinary text is that rich text has format, # Product details are often not plain text but formatted # The logo field is not a picture type but a CharField, # Because the logo of the front end is not a directly specified image, but a sprite image, # Use class to specify different logo s, and this CharField is the class name # The choices field is used to limit the value range
To install and configure tinymce for the HTML field used here:
tinymce is just a kind of rich text editor. After logging in to the admin interface, the field is edited with rich text editor. When it is saved in the database, it is an html string, and its sql type is longtext;
# settings.py Add # Rich text editor configuration TINYMCE_DEFAULT_CONFIG = { 'theme': 'advanced', 'width': 600, 'height': 400, }
# urls.py Add url urlpatterns = [ path('admin/', admin.site.urls), path('tinymce/',include('tinymce.urls')), # Rich Text Editor path('user/', include('user.urls', namespace='user')), path('order/', include('order.urls', namespace='order')), path('cart/', include('cart.urls', namespace='cart')), path('', include('goods.urls', namespace='goods')), ]
The ImageField used should also be installed:
order/models.py:
from django.db import models from db.base_model import BaseModel class OrderInfo(BaseModel): '''Order model class''' PAY_METHOD_CHOICES = ( (1, 'Cash on Delivery'), (2, 'Wechat payment'), (3, 'Alipay'), (4, 'UnionPay payment') ) ORDER_STATUS_CHOICES = ( (1, 'To be paid'), (2, 'To be delivered'), (3, 'To be received'), (4, 'To be evaluated'), (5, 'Completed') ) order_id = models.CharField(max_length=128, primary_key=True, verbose_name='order id') user = models.ForeignKey('user.User', verbose_name='user') addr = models.ForeignKey('user.Address', verbose_name='address') pay_method = models.SmallIntegerField(choices=PAY_METHOD_CHOICES, default=3, verbose_name='Payment method') total_count = models.IntegerField(default=1, verbose_name='Quantity of goods') total_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='Total price of goods') transit_price = models.DecimalField(max_digits=10, decimal_places=2,verbose_name='Order freight') order_status = models.SmallIntegerField(choices=ORDER_STATUS_CHOICES, default=1, verbose_name='Order status') trade_no = models.CharField(max_length=128, verbose_name='Payment No') class Meta: db_table = 'df_order_info' verbose_name = 'order' verbose_name_plural = verbose_name class OrderGoods(BaseModel): '''Order item model class''' order = models.ForeignKey('OrderInfo', verbose_name='order') sku = models.ForeignKey('goods.GoodsSKU', verbose_name='commodity SKU') count = models.IntegerField(default=1, verbose_name='Number of goods') price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='commodity price') comment = models.CharField(max_length=256, verbose_name='comment') class Meta: db_table = 'df_order_goods' verbose_name = 'Order line item' verbose_name_plural = verbose_name # Payment number is the number generated after Alipay payment.
13. Specify the model class used by Django authentication system
# settings.py Add # Model classes used by Django authentication system AUTH_USER_MODEL = 'user.User'
If not specified, Django has its own model class. After migration, those fields (user name, password, mailbox activation, administrator or not) will be created in an auth_user's table, not our own user.User , and the reason our user model class does not define any fields is that Django automatically creates fields in order to specify user as the authentication model here.
If not specified, when Python is executed manage.py When createsuperuser is created, the generated administrator user will be added to auth_ In the user table, when specified, it is added to the specified table.
Field is_ super_ If user = 1, it means administrator user, and field is_ If staff = 1, it means that it is an ordinary user;
14. Migration
These two steps are not smooth sailing. The first step is to report an error:
reason:
One to many relationship (foreign key ForeignKey) to specify on_delete attribute, which specifies how to handle the foreign key if it is deleted. Refer to: https://blog.csdn.net/KreaWu/article/details/89400647
(the above code has been changed)
Step 2 error reporting:
Specifying a namespace in include() without providing an app_name is not supported. Set the app_name attribute in the included module, or pass a 2-tuple containing the list of patterns and app_name instead.
reason:
App not set_ Name, modify:
# apps/cart/urls.py from django.urls import path app_name = 'cart' urlpatterns = [ ]
# apps/goods/urls.py from django.urls import path app_name = 'goods' urlpatterns = [ ]
# apps/order/urls.py from django.urls import path app_name = 'order' urlpatterns = [ ]
# apps/user/urls.py from django.urls import path app_name = 'user' urlpatterns = [ ]
For each application urls.py Add app before urlpatterns = []_ name = 'xxxx' ;
After migration, tables are created in the database:
15. Start server
Copy the static page to the static Directory:
So far, the framework has been completed, and there are two small points:
1)verbose_name and verbose_name_plural
Both model classes and fields can specify verbose_name is the name displayed in the admin interface;
The model class can also specify verbose_name_plural, specifying verbose_ Plural form of name;
If verbose in class Meta of model class_ Name = product, then the table name displayed in the admin interface is product s. if you add verbose_name_plural = verbose_name indicates that the table name is commodity, that is, the plural number is the same as the singular number;
Project directory/ admin.py To register model classes in:
2) The ImageField type field in the model class has an attribute upload_to='goods'this is for Django's built-in file storage system. When uploading pictures on the admin interface, they will be saved to the goods folder. However, we do not use Django's built-in file storage system, but use a distributed file storage system, so this field property does not matter;
16. User registration
hold register.html Copy to the templates directory;
Write view function:
# user/views.py from django.shortcuts import render # /user/register def register(request): '''Show registration page''' return render(request, 'register.html')
Configure routes:
# user/urls.py from django.urls import path from apps.user import views app_name = 'user' urlpatterns = [ path('register', views.register, name='register') ]
Browser Test:
To modify a style path:
{% load static%} {import static file path {} {% static 'css/reset.css '%} {all static files are written in this form {}
To modify form properties:
{# register.html #} <form method="post" action="user/register_handle"> {% csrf_token %} {# use Django Of csrf protect #}
Define view functions:
#user/views.py Add # /user/register_handle def register_handle(request): '''Registration processing''' # Basic steps of trying function: receiving data, data verification, business processing, returning response username = request.POST.get('user_name') password = request.POST.get('pwd') email = request.POST.get('email') allow = request.POST.get('allow') if not all([username, password, email]): # all tests are not empty return render(request, 'register.html', {'errmsg': 'Incomplete data'}) if not re.match(r'^[a-z0-9][\w.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email): return render(request, 'register.html', {'errmsg': 'Incorrect mailbox format'}) if allow != 'on': return render(request, 'register.html', {'errmsg': 'Please agree to the agreement'}) user = User.objects.create_user(username, email, password) return redirect(reverse('goods:index')) # namespace:name # redirect redirection, reverse parsing # Reverse parsing is to dynamically generate paths based on namespace and name, without having to write the paths dead # Pay attention to reverse parsing namespace:name Do not have space after colon!!!!!!!!!
Configure routes:
# user/urls.py Add url from django.urls import path from apps.user import views app_name = 'user' urlpatterns = [ path('register/', views.register, name='register'), # register path('register_handle', views.register_handle, name='register_handle'), # Registration processing ]
The redirected index page should also be configured:
Put static/index.html Copy to the templates directory;
# goods/views.py from django.shortcuts import render # 127.0.0.1:8000 def index(requst): '''home page''' return render(requst, 'index.html')
# goods/urls.py from django.urls import path from apps.goods import views app_name = 'goods' urlpatterns = [ path('', views.index, name='index'), # home page ]
Browser Test: (unified user password 11111111)
Click Register to jump to:
View database, user created successfully: (\ G let the results be displayed in rows)
The password is encrypted (industry standard); is_active=1 no, we need to make the newly registered account inactive, and then activate it through email, so:
# user/ views.py register_ Handle modification user = User.objects.create_user(username, email, password) user.is_active = 0 user.save()
Two paths are used for registration page and registration processing, and one path can be used instead:
# user/views.py import re from django.shortcuts import render, redirect from django.urls import reverse from apps.user.models import User # /user/register def register(request): if request.method == 'GET': # Show registration page return render(request, 'register.html') elif request.method == 'POST': # Registration processing # Basic steps of trying function: receiving data, data verification, business processing, returning response username = request.POST.get('user_name') password = request.POST.get('pwd') email = request.POST.get('email') allow = request.POST.get('allow') if not all([username, password, email]): # all tests are not empty return render(request, 'register.html', {'errmsg': 'Incomplete data'}) if not re.match(r'^[a-z0-9][\w.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email): return render(request, 'register.html', {'errmsg': 'Incorrect mailbox format'}) if allow != 'on': return render(request, 'register.html', {'errmsg': 'Please agree to the agreement'}) user = User.objects.create_user(username, email, password) user.is_active = 0 user.save() return redirect(reverse('goods:index')) # namespace:name # redirect redirection, reverse parsing # Reverse parsing is to dynamically generate paths based on namespace and name, without having to write the paths dead # Pay attention to reverse parsing namespace:name Do not have space after colon!!!!!!!!!
# user/urls.py from django.urls import path from apps.user import views app_name = 'user' urlpatterns = [ path('register', views.register, name='register'), # register ] # As the last section of the path, path('register ') cannot have /, otherwise Page Not Found will be reported
# templates/register.html Change the form submission path <form method="post" action="/user/register">
So far, method view is used. Different request methods need to define different methods. Now use class view instead, define a view class, corresponding to a path, define get() and post() member methods, corresponding to different requests:
# user/views.py import re from django.shortcuts import render, redirect from django.urls import reverse from django.views import View from apps.user.models import User # /user/register class RegisterView(View): '''register''' def get(self, request): # Show registration page return render(request, 'register.html') def post(self, request): # Registration processing # Basic steps of trying function: receiving data, data verification, business processing, returning response username = request.POST.get('user_name') password = request.POST.get('pwd') email = request.POST.get('email') allow = request.POST.get('allow') if not all([username, password, email]): # all tests are not empty return render(request, 'register.html', {'errmsg': 'Incomplete data'}) if not re.match(r'^[a-z0-9][\w.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email): return render(request, 'register.html', {'errmsg': 'Incorrect mailbox format'}) if allow != 'on': return render(request, 'register.html', {'errmsg': 'Please agree to the agreement'}) user = User.objects.create_user(username, email, password) user.is_active = 0 user.save() return redirect(reverse('goods:index')) # namespace:name # redirect redirection, reverse parsing # Reverse parsing is to dynamically generate paths based on namespace and name, without having to write the paths dead # Pay attention to reverse parsing namespace:name Do not have space after colon!!!!!!!!!
# user/urls.py modify path('register', views.RegisterView.as_view(), name='register'), # register
Class view principle:
dispatch() method, which is distributed to different methods according to different request methods. In fact, the method string is obtained and converted to lowercase as the method name to call the method;
17. User activation
After the user registers, he sends a link to the mailbox. After the user clicks the link, the server activates the user according to the identity information in the link. The user's identity information can use the user's id in the data table, but it can't be clear text, because once the user sees that it is an id, it will be attacked, so it uses its dangerous encryption;
Install itsdangerous: (if you want to install timeout later, you can use PyCharm's Terminal to install it.)
Test its dangerous:
Serializer (key, expiration time) creates objects, dumps (Json) encrypts, and loads (byte string) decrypts;
As shown in the figure, as long as the key is the same, it can be decrypted. After the key expires, the decryption reports an error:
163 mailbox opens smtp service:
Will send the verification code to the mobile phone, set the authorization code after the verification is successful, save the authorization code, and use it when sending emails;
Send mail:
# settings.py Add # Send mail configuration EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST = 'smtp.163.com' EMAIL_PORT = 25 EMAIL_HOST_USER = 'django_core_mail@163.com' # Email address EMAIL_HOST_PASSWORD = 'django2019' # Authorization code EMAIL_FROM = 'DailyFresh<django_core_mail@163.com>' # Sender nickname < email_ HOST_ USER>
# user/views.py Modify registration view # /user/register class RegisterView(View): '''register''' def get(self, request): # Show registration page return render(request, 'register.html') def post(self, request): # Registration processing # Basic steps of trying function: receiving data, data verification, business processing, returning response username = request.POST.get('user_name') password = request.POST.get('pwd') email = request.POST.get('email') allow = request.POST.get('allow') if not all([username, password, email]): # all tests are not empty return render(request, 'register.html', {'errmsg': 'Incomplete data'}) if not re.match(r'^[a-z0-9][\w.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email): return render(request, 'register.html', {'errmsg': 'Incorrect mailbox format'}) if allow != 'on': return render(request, 'register.html', {'errmsg': 'Please agree to the agreement'}) user = User.objects.create_user(username, email, password) user.is_active = 0 user.save() seri = Serializer(settings.SECRET_KEY, 600) info = {'confirm': user.id} token = seri.dumps(info).decode('utf8') # bytes after encryption, turn to utf-8 subject = 'Daily fresh user activation' message = 'Stay indoors, fresh every day' sender = settings.EMAIL_FROM receiver_list = [email] html_message = '<h1>%s,Welcome to daily fresh</h1>Please click on the link to activate the user:<br/>' \ '<a href="http://127.0.0.1:8000/user/active/%s">' \ 'http://127.0.0.1:8000/user/active/%s</a>' % (username, token, token) send_mail(subject, message, sender, receiver_list, html_message=html_message) return redirect(reverse('goods:index')) # namespace:name # redirect redirection, reverse parsing # Reverse parsing is to dynamically generate paths based on namespace and name, without having to write the paths dead # Pay attention to reverse parsing namespace:name Do not have space after colon!!!!!!!!! # message is plain text, html_message can be html, note html_message is a key parameter # With html_message, message will not be displayed
# user/views.py Add # user/active class ActiveView(View): '''User activation''' def get(self, request, token): seri = Serializer(settings.SECRET_KEY, 600) try: info = seri.loads(token) id = info['confirm'] user = User.objects.get(id=id) user.is_active = 1 user.save() return redirect(reverse('user:login')) except SignatureExpired as e: # Activation link expired return HttpResponse('Activation link expired') # user/login class LoginView(View): '''Sign in''' def get(self, request): '''Show login page''' return render(request, 'login.html')
# user/urls.py Add url path('active/<token>', views.ActiveView.as_view(), name='active'), # activation path('login', views.LoginView.as_view(), name='login'), # Sign in # Capture parameters with < > in the path
Click Register to go to the home page:
View database, is_active=0:
Check the email (may be in the dustbin), click the link to activate and jump to the login page:
View database, is_active=1:
Activation complete.
PS: difference between two packages:
View: django.views.View or django.views.generic.base.View?
settings: dailyfresh.settings or django.conf.settings?
18. Send mail asynchronously
Django's built-in send_email() is blocked and should be asynchronous for user experience; any time-consuming operation should be asynchronous;
Cell ry task queue mechanism:
To install cellery:
# Because redis cluster needs redis2.10.6, and the latest version of corery's dependency package kombu is not compatible with redis2.10.6 # Therefore, the lower version of kombu must be used. Because of the dependency, the lower version of cellery is also required (env1fordjango) pip3 install kombu==4.2.0 (env1fordjango) pip3 install celery==4.2.0
New python package, python file: cellery_ tasks.tasks
Define task functions
# celery_tasks/tasks.py from celery import Celery from django.core.mail import send_mail from dailyfresh import settings # Initialize Django environment variable, which is used by task executor import os import django os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dailyfresh.settings') django.setup() # Create a cell ry instance object, write parameter 1 casually, parameter 2 is the intermediary, local redis, database 8 app = Celery('celery_tasks.tasks', broker='redis://127.0.0.1:6379/8') # Define task function, a task is a function @app.task def send_active_email(to_email, username, token): subject = 'Daily fresh user activation' message = 'Stay indoors, fresh every day' sender = settings.EMAIL_FROM receiver_list = [to_email] html_message = '<h1>%s,Welcome to daily fresh</h1>Please click on the link to activate the user:<br/>' \ '<a href="http://127.0.0.1:8000/user/active/%s">' \ 'http://127.0.0.1:8000/user/active/%s</a>' % (username, token, token) send_mail(subject, message, sender, receiver_list, html_message=html_message)
Call the task function and issue the task:
# user/views.py Modify the registration view to change the email code to the sending task # /user/register class RegisterView(View): '''register''' def get(self, request): # Show registration page return render(request, 'register.html') def post(self, request): # Registration processing # Basic steps of trying function: receiving data, data verification, business processing, returning response username = request.POST.get('user_name') password = request.POST.get('pwd') email = request.POST.get('email') allow = request.POST.get('allow') if not all([username, password, email]): # all tests are not empty return render(request, 'register.html', {'errmsg': 'Incomplete data'}) if not re.match(r'^[a-z0-9][\w.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email): return render(request, 'register.html', {'errmsg': 'Incorrect mailbox format'}) if allow != 'on': return render(request, 'register.html', {'errmsg': 'Please agree to the agreement'}) user = User.objects.create_user(username, email, password) user.is_active = 0 user.save() seri = Serializer(settings.SECRET_KEY, 600) info = {'confirm': user.id} token = seri.dumps(info).decode('utf8') # bytes after encryption, turn to utf-8 # Issue task send_active_email.delay(email, username, token) return redirect(reverse('goods:index')) # namespace:name # redirect redirection, reverse parsing # Reverse parsing is to dynamically generate paths based on namespace and name, without having to write the paths dead # Pay attention to reverse parsing namespace:name Do not have space after colon!!!!!!!!! # message is plain text, html_message can be html, note html_message is a key parameter # With html_message, message will not be displayed
Start the redis service because the man in the middle specified in the task function is 127.0.0.1 redis.conf bind 127.0.0.1 is also required to be consistent in, otherwise the connection cannot be reached;
Start task executor, cellery_ tasks.tasks Is the py file where the specified task function is located, - l info is the print log information:
The yellow warning is not for cellery, but for Django, which means that opening Debug will lead to memory leakage. Do not open Debug in the production environment settings.py In this way, there is no warning, but there is no need, because we are not the production environment:
Registration:
Message received successfully:
The performer also prints information:
be careful:
1) Cellery doesn't work independently. It's just a tool of Django. It needs Django environment to work;
2) How to deploy the daily fresh project (task sender) and executor on different machines: copy the complete project (dailyfresh directory) to the executor's machine, start the executor, the sender does not need to initialize the Django environment variable, and the initiator must initialize it.
19. User login
login.html Change static file path (same as register.html ):
Test:
Modify form:
If action is not specified, it is submitted to the address of the current address column, and we just want it to be submitted to this address. Like register, it uses the same address for display and processing, so action is not specified;
Define view:
# user/views.py Log in view add post method # user/login class LoginView(View): '''Sign in''' def get(self, request): '''Show login page''' return render(request, 'login.html') def post(self, request): username = request.POST.get('username') password = request.POST.get('pwd') if not all([username, password]): return render(request, 'login.html', {'errmsg': 'Incomplete data'}) # Django's built-in authentication system verifies the user name and password (automatically encrypts the password and then queries it) user = authenticate(username=username, password=password) print(user) if user is not None: if user.is_active: login(request, user) # Django's built-in authentication system records the user's login status to the session return redirect(reverse('goods:index')) else: return render(request, 'login.html', {'errmsg': 'User not activated'}) else: return render(request, 'login.html', {'errmsg': 'Wrong user name and password'})
Using authenticate() will automatically detect is_active field, even if found through username and password, if is_active=0 will also return None, and cancel detection:
# settings.py Add # Is will not be detected when using Django built-in authentication system_ Active field AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.AllowAllUsersModelBackend']
Find a suitable location to output errmsg:
19. Set to use cache to store session, and set to use redis as cache
Installation:
pip3 install django-redis # Installing Django redis will automatically install redis 3.5.0 pip3 install redis==2.10.6 # redis2.10.6 is needed to build the cluster and then install it back
to configure:
# settings.py Middle append # Django cache setting. The default memory of this machine is cache, which can be set to redis CACHES = { 'default':{ 'BACKEND': 'django_redis.cache.RedisCache', 'LOCATION': 'redis://127.0.0.1:6379 / 9 ', address of redis service routine 'OPTION': { 'CLIENT_CLASS': 'django_redis.client.DefaultClient' } } } # Django session setting, database by default, can be set as cache or redis SESSION_ENGINE = 'django.contrib.sessions.backends.cache' SESSION_CACHE_ALIAS = 'default'
Start the redis service:
Enter the client, now there is no key:
Log in and look again:
20. Remember user name
# user/views.py Modify get and post methods of login view # user/login class LoginView(View): '''Sign in''' def get(self, request): '''Show login page''' # Judge whether the user name is remembered if 'username' in request.COOKIES: username = request.COOKIES.get('username') checked = 'checked' else: username = '' checked = '' return render(request, 'login.html', {'username': username, 'checked': checked}) def post(self, request): username = request.POST.get('username') password = request.POST.get('pwd') if not all([username, password]): return render(request, 'login.html', {'errmsg': 'Incomplete data'}) user = authenticate(username=username, password=password) print(user) if user is not None: if user.is_active: login(request, user) # Django's built-in authentication system records the user's login status to the session response = redirect(reverse('goods:index')) # If checked, the user name will be saved to the cookie. If unchecked, the user name in the cookie will be cleared remember = request.POST.get('remember') if remember == 'on': response.set_cookie('username', username, max_age=7*24*3600) # cookie expires one week else: response.delete_cookie('username') return response else: return render(request, 'login.html', {'errmsg': 'User not activated'}) else: return render(request, 'login.html', {'errmsg': 'Wrong user name and password'})
Parameters used in template:
effect:
15. Extract parent template:
hold index.html Copy to templates, rename to base.html ;
Each page has some parts, left;
Some pages have, some do not have the part, writes in the block when the default value, then does not have the direct deletion;
Each page has different parts, delete them and leave a block;
{homepage, registration, login}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
{% load staticfiles %}
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
Page title block
<title>{% block title %}{% endblock title %}</title>
{css {used for every page}
<link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
{insert file block at the top of web page}
{% block topfiles %}{% endblock topfiles %}
</head>
<body>
{ා welcome information block at the top of the web page, some pages have, some have not, so there is a default value. If not, directly clear ා}
{% block header_con %}
<div class="header_con">...</div>
{% endblock header_con %}
{search box at the top of web page {}
{% block search_bar %}
<div class="search_bar clearfix">
<a href="index.html" class="logo fl"><img src="images/logo.png"></a>
<div class="search_con fl">
<input type="text" class="input_ Text FL "name =" "placeholder =" search product ">
<input type="button" class="input_ BTN fr "name =" "value =" search ">
</div>
<div class="guest_cart fr">
<a href="#" class="cart_ Name FL "> my shopping cart</a>
<div class="goods_count fl" id="show_count">1</div>
</div>
</div>
{% endblock search_bar %}
{web page main content block {}
{% block body %}{% endblock body %}
<div class="footer">....</div>
{html element block at the bottom of web page {}
{% block bottom %}{% endblock bottom %}
{insert file block at the bottom of web page}
{% block bottomfiles %}{% endblock bottomfiles %}
</body>
</html>