程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

[Python 100 day learning notes] introduction and use of day44 web form

編輯:Python

Application of forms

Let's continue to complete the project in the previous chapter , Realization “ User registration ” and “ The user login ” The function of , And restrict only logged in users to vote for teachers .Django The framework provides the encapsulation of forms , And provides a variety of different ways to use .

First add the user model .

class User(models.Model):
""" user """
no = models.AutoField(primary_key=True, verbose_name=' Number ')
username = models.CharField(max_length=20, unique=True, verbose_name=' user name ')
password = models.CharField(max_length=32, verbose_name=' password ')
regdate = models.DateTimeField(auto_now_add=True, verbose_name=' Registration time ')
class Meta:
db_table = 'tb_user'
verbose_name_plural = ' user '

By generating migration and performing migration operations , Create the corresponding user table in the database .

(venv)$ python manage.py makemigrations vote
...
(venv)$ python manage.py migrate
...

Customize a very simple registration template page .

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> User registration </title>
<style>/* The cascading style sheet selector is omitted here */</style>
</head>
<body>
<h1> User registration </h1>
<hr>
<p class="hint">{
{ hint }}</p>
<form action="/register/" method="post">
{% csrf_token %}
<div class="input">
<label for="username"> user name :</label>
<input type="text" id="username" name="username">
</div>
<div class="input">
<label for="password"> password :</label>
<input type="password" id="password" name="password">
</div>
<div class="input">
<label for="repassword"> Confirm the password :</label>
<input type="password" id="repassword" name="repassword">
</div>
<div class="input">
<input type="submit" value=" register ">
<input type="reset" value=" Reset ">
</div>
</form>
<a href="/login"> Return to login </a>
</body>
</html>

Be careful , In the form above , We used template instructions {% csrf_token %} Add a hidden field to the form (type The property value is hidden Of input label ), Its function is to generate a random token in the form (token) To guard against Cross-site request forgery ( Usually abbreviated as CSRF), This is also Django Rigid requirements when submitting forms , Unless we set an exemption CSRF token . The following figure is about CSRF Simple and vivid examples , It comes from Wikipedia .

When the user submits the registration form , We also need to verify the user's input , For example, our website requires that the user name must be composed of letters 、 Numbers 、 The underline is formed and the length is 4-20 Between characters , The length of the password is 8-20 Characters , The confirmation password must be consistent with the password . These verification operations can first be performed through JavaScript Code to complete , But even so , On the server side, it is still necessary to verify the user input again to avoid giving the invalid database to the database , Because the user may disable the browser JavaScript function , It is also possible to bypass the input check of the browser and submit the registration data to the server , So user input checking on the server side is still necessary .

We can use Django The framework encapsulates the form function to check the validity of user input , although Django The encapsulated form can also help us customize the form elements on the page , But this is obviously a design with poor flexibility , Such functions are basically not considered in actual development , So the main function of forms is data validation , The specific methods are as follows .

USERNAME_PATTERN = re.compile(r'\w{4,20}')
class RegisterForm(forms.ModelForm):
repassword = forms.CharField(min_length=8, max_length=20)
def clean_username(self):
username = self.cleaned_data['username']
if not USERNAME_PATTERN.fullmatch(username):
raise ValidationError(' The user name consists of letters 、 Numbers and underscores are formed and the length is 4-20 Characters ')
return username
def clean_password(self):
password = self.cleaned_data['password']
if len(password) < 8 or len(password) > 20:
raise ValidationError(' Invalid password , The length of the password is 8-20 Characters ')
return to_md5_hex(self.cleaned_data['password'])
def clean_repassword(self):
repassword = to_md5_hex(self.cleaned_data['repassword'])
if repassword != self.cleaned_data['password']:
raise ValidationError(' The password and confirmation password do not match ')
return repassword
class Meta:
model = User
exclude = ('no', 'regdate')

above , We define a relation with User Model bound form ( Inherited from ModelForm), We exclude user numbers (no) And date of registration (regdate) These two properties , And added one repassword Property is used to receive the confirmation password passed from the user form to the server . We are defining User The maximum length of user name has been limited when modeling , Above, we also limit the minimum and maximum length of the confirmation password , But these are not enough to complete our verification of user input . On top of it clean_ The starting method is our custom validation rules . Obviously ,clean_username It is to check the user name , and clean_password It's checking the password . Because the original password should not be saved in the two-dimensional table of the database , So I made a simple password MD5 Abstract processing , In the actual development, it is not enough to deal with it like this , Because even if the summary is used , There is still a risk of using rainbow table to reverse query and crack user passwords , We will talk about how to do better in the following content . Generate for string MD5 The code of the abstract is as follows .

def to_md5_hex(message):
return hashlib.md5(message.encode()).hexdigest()

Add a view function to realize the function of user registration .

def register(request):
page, hint = 'register.html', ''
if request.method == 'POST':
form = RegisterForm(request.POST)
if form.is_valid():
form.save()
page = 'login.html'
hint = ' Registered successfully , Please log in '
else:
hint = ' Please enter valid registration information '
return render(request, page, {
'hint': hint})

If the user initiates GET request , Will jump directly to the registration page ; If the user uses POST Submit the registration form , Then create a custom registration form object and get user input . You can use the is_valid Method to verify the form , If there is no problem with user input , This method returns True, Otherwise return to False; Because we define RegisterForm Inherited from ModelForm, Therefore, you can also directly use the form object save Method to save the model . Here is the registration request URL To configure .

from django.contrib import admin
from django.urls import path
from vote import views
urlpatterns = [
# The above code is omitted here 
path('register/', views.register, name='register'),
# The following code is omitted here 
]

explain :path Functions can be name Parameters to URL Bind a reverse resolved name , in other words , If necessary, you can reverse parse the corresponding name from the name given later URL.

Let's customize a very simple landing page .

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> The user login </title>
<style>/* The cascading style sheet selector is omitted here */</style>
</head>
<body>
<h1> The user login </h1>
<hr>
<p class="hint">{
{ hint }}</p>
<form action="/login/" method="post">
{% csrf_token %}
<div class="input">
<label for="username"> user name :</label>
<input type="text" id="username" name="username">
</div>
<div class="input">
<label for="password"> password :</label>
<input type="password" id="password" name="password">
</div>
<div class="input captcha">
<label for="captcha"> Verification Code :</label>
<input type="text" id="captcha" name="captcha">
<img src="/captcha/" width="120">
</div>
<div class="input">
<input type="submit" value=" Sign in ">
<input type="reset" value=" Reset ">
</div>
</form>
<a href="/register"> Register new users </a>
</body>
</html>

In the login page above , We require users to provide verification code , The full name of the verification code is Fully automated Public Turing test to distinguish between computers and humans , It is a program used to distinguish whether the user of the system is a computer or a human . Simply put, the program produces a question that only human beings can answer , It is up to the system user to answer , Because the computer cannot theoretically solve the problems raised by the program , So the user who answers the question can be considered human . Most websites use different types of verification code technology to prevent automatic user registration or simulated user login ( Brute force cracking user passwords ), Because the verification code has one-time consumption , But the program that has not passed the Turing test cannot complete the registration or login .

stay Python Generating verification code in the program is not particularly complex , But a third-party library is required Pillow Support for (PIL The branch of ), Because the verification code image needs to be rotated 、 Distortion 、 Stretch and add interference information to prevent those who use OCR( Optical character recognition ) Program to crack the verification code . The following code encapsulates the function of generating verification code pictures , You can directly use these codes to generate image verification codes , Don't “ Reinventing the wheel ”.

""" Image verification code """
import os
import random
from io import BytesIO
from PIL import Image
from PIL import ImageFilter
from PIL.ImageDraw import Draw
from PIL.ImageFont import truetype
class Bezier(object):
""" Bessel curve """
def __init__(self):
self.tsequence = tuple([t / 20.0 for t in range(21)])
self.beziers = {
}
def make_bezier(self, n):
""" Draw the Bessel curve """
try:
return self.beziers[n]
except KeyError:
combinations = pascal_row(n - 1)
result = []
for t in self.tsequence:
tpowers = (t ** i for i in range(n))
upowers = ((1 - t) ** i for i in range(n - 1, -1, -1))
coefs = [c * a * b for c, a, b in zip(combinations,
tpowers, upowers)]
result.append(coefs)
self.beziers[n] = result
return result
class Captcha(object):
""" Verification Code """
def __init__(self, width, height, fonts=None, color=None):
self._image = None
self._fonts = fonts if fonts else \
[os.path.join(os.path.dirname(__file__), 'fonts', font)
for font in ['ArialRB.ttf', 'ArialNI.ttf', 'Georgia.ttf', 'Kongxin.ttf']]
self._color = color if color else random_color(0, 200, random.randint(220, 255))
self._width, self._height = width, height
@classmethod
def instance(cls, width=200, height=75):
prop_name = f'_instance_{
width}_{
height}'
if not hasattr(cls, prop_name):
setattr(cls, prop_name, cls(width, height))
return getattr(cls, prop_name)
def background(self):
""" Draw the background """
Draw(self._image).rectangle([(0, 0), self._image.size],
fill=random_color(230, 255))
def smooth(self):
""" Smooth the image """
return self._image.filter(ImageFilter.SMOOTH)
def curve(self, width=4, number=6, color=None):
""" draw a curve """
dx, height = self._image.size
dx /= number
path = [(dx * i, random.randint(0, height))
for i in range(1, number)]
bcoefs = Bezier().make_bezier(number - 1)
points = []
for coefs in bcoefs:
points.append(tuple(sum([coef * p for coef, p in zip(coefs, ps)])
for ps in zip(*path)))
Draw(self._image).line(points, fill=color if color else self._color, width=width)
def noise(self, number=50, level=2, color=None):
""" Draw scrambling code """
width, height = self._image.size
dx, dy = width / 10, height / 10
width, height = width - dx, height - dy
draw = Draw(self._image)
for i in range(number):
x = int(random.uniform(dx, width))
y = int(random.uniform(dy, height))
draw.line(((x, y), (x + level, y)),
fill=color if color else self._color, width=level)
def text(self, captcha_text, fonts, font_sizes=None, drawings=None, squeeze_factor=0.75, color=None):
""" Draw text """
color = color if color else self._color
fonts = tuple([truetype(name, size)
for name in fonts
for size in font_sizes or (65, 70, 75)])
draw = Draw(self._image)
char_images = []
for c in captcha_text:
font = random.choice(fonts)
c_width, c_height = draw.textsize(c, font=font)
char_image = Image.new('RGB', (c_width, c_height), (0, 0, 0))
char_draw = Draw(char_image)
char_draw.text((0, 0), c, font=font, fill=color)
char_image = char_image.crop(char_image.getbbox())
for drawing in drawings:
d = getattr(self, drawing)
char_image = d(char_image)
char_images.append(char_image)
width, height = self._image.size
offset = int((width - sum(int(i.size[0] * squeeze_factor)
for i in char_images[:-1]) -
char_images[-1].size[0]) / 2)
for char_image in char_images:
c_width, c_height = char_image.size
mask = char_image.convert('L').point(lambda i: i * 1.97)
self._image.paste(char_image,
(offset, int((height - c_height) / 2)),
mask)
offset += int(c_width * squeeze_factor)
@staticmethod
def warp(image, dx_factor=0.3, dy_factor=0.3):
""" Image warping """
width, height = image.size
dx = width * dx_factor
dy = height * dy_factor
x1 = int(random.uniform(-dx, dx))
y1 = int(random.uniform(-dy, dy))
x2 = int(random.uniform(-dx, dx))
y2 = int(random.uniform(-dy, dy))
warp_image = Image.new(
'RGB',
(width + abs(x1) + abs(x2), height + abs(y1) + abs(y2)))
warp_image.paste(image, (abs(x1), abs(y1)))
width2, height2 = warp_image.size
return warp_image.transform(
(width, height),
Image.QUAD,
(x1, y1, -x1, height2 - y2, width2 + x2, height2 + y2, width2 - x2, -y1))
@staticmethod
def offset(image, dx_factor=0.1, dy_factor=0.2):
""" Image offset """
width, height = image.size
dx = int(random.random() * width * dx_factor)
dy = int(random.random() * height * dy_factor)
offset_image = Image.new('RGB', (width + dx, height + dy))
offset_image.paste(image, (dx, dy))
return offset_image
@staticmethod
def rotate(image, angle=25):
""" Image rotation """
return image.rotate(random.uniform(-angle, angle),
Image.BILINEAR, expand=1)
def generate(self, captcha_text='', fmt='PNG'):
""" Generate verification code ( Words and pictures )"""
self._image = Image.new('RGB', (self._width, self._height), (255, 255, 255))
self.background()
self.text(captcha_text, self._fonts,
drawings=['warp', 'rotate', 'offset'])
self.curve()
self.noise()
self.smooth()
image_bytes = BytesIO()
self._image.save(image_bytes, format=fmt)
return image_bytes.getvalue()
def pascal_row(n=0):
""" Generate Pascal Triangle n That's ok """
result = [1]
x, numerator = 1, n
for denominator in range(1, n // 2 + 1):
x *= numerator
x /= denominator
result.append(x)
numerator -= 1
if n & 1 == 0:
result.extend(reversed(result[:-1]))
else:
result.extend(reversed(result))
return result
def random_color(start=0, end=255, opacity=255):
""" Get random colors """
red = random.randint(start, end)
green = random.randint(start, end)
blue = random.randint(start, end)
if opacity is None:
return red, green, blue
return red, green, blue, opacity

explain : The above code uses three font files when generating the verification code image , When using the above code, you need to add the font file to the... In the application directory fonts Directory .

The following view function is used to generate the verification code and pass HttpResponse Object to the user browser .

ALL_CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
def get_captcha_text(length=4):
selected_chars = random.choices(ALL_CHARS, k=length)
return ''.join(selected_chars)
def get_captcha(request):
""" Get the verification code """
captcha_text = get_captcha_text()
image = Captcha.instance().generate(captcha_text)
return HttpResponse(image, content_type='image/png')

The generated verification code is shown in the figure below .

To verify the login form submitted by the user , Let's define another form class .

class LoginForm(forms.Form):
username = forms.CharField(min_length=4, max_length=20)
password = forms.CharField(min_length=8, max_length=20)
captcha = forms.CharField(min_length=4, max_length=4)
def clean_username(self):
username = self.cleaned_data['username']
if not USERNAME_PATTERN.fullmatch(username):
raise ValidationError(' Invalid user name ')
return username
def clean_password(self):
return to_md5_hex(self.cleaned_data['password'])

It is slightly different from the registration form class we defined before , The login form class directly inherits from Form Not bound to the model , Three fields are defined corresponding to the user name in the login form 、 Password and verification code . Next is the view function that handles user login .

def login(request):
hint = ''
if request.method == 'POST':
form = LoginForm(request.POST)
if form.is_valid():
username = form.cleaned_data['username']
password = form.cleaned_data['password']
user = User.objects.filter(username=username, password=password).first()
if user:
return redirect('/')
else:
hint = ' Wrong user name or password '
else:
hint = ' Please enter valid login information '
return render(request, 'login.html', {
'hint': hint})

mapping URL.

from django.contrib import admin
from django.urls import path
from vote import views
urlpatterns = [
# The above code is omitted here 
path('login/', views.login, name='login'),
# The following code is omitted here 
]

Need to point out that , Above, we set the user to return to the home page directly when logging in successfully , And when the user logs in, he does not verify whether the verification code entered by the user is correct , Let's save these for the next unit to explain . in addition , If you want to in Django Form verification is carried out in the built-in management background , Can be in admin.py Specified in the model management class of form The attribute is a custom form , for example :

class UserForm(forms.ModelForm):
password = forms.CharField(min_length=8, max_length=20,
widget=forms.PasswordInput, label=' password ')
def clean_username(self):
username = self.cleaned_data['username']
if not USERNAME_PATTERN.fullmatch(username):
raise ValidationError(' The user name consists of letters 、 Numbers and underscores are formed and the length is 4-20 Characters ')
return username
def clean_password(self):
password = self.cleaned_data['password']
return to_md5_hex(self.cleaned_data['password'])
class Meta:
model = User
exclude = ('no', )
class UserAdmin(admin.ModelAdmin):
list_display = ('no', 'username', 'password', 'email', 'tel')
ordering = ('no', )
form = UserForm
list_per_page = 10
admin.site.register(User, UserAdmin)

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved