[英]Improving Python/django view code
我对 Python/Django 和一般编程非常陌生。 由于我的编程包里的工具有限,我为用户注册后编写了三个视图函数:它允许用户在激活他的帐户之前添加信息和上传缩略图。
我已经发布了到目前为止我编写的代码,以便比我有更多经验的人可以向我展示如何改进代码。 毫无疑问,这是具有新手所有标记的粗略代码,但我从编写代码中学到的东西最好——寻找改进它的方法和学习新工具——然后重写它。
我知道回答这个问题需要相当长的时间。 因此,我将为此问题奖励 200 分。 所以只允许我在问题发布两天后添加赏金,所以我将在星期二为这个问题添加赏金(一旦可以添加)。 请注意,由于在发布赏金之前我不会选择答案,因此在添加赏金之前提供的答案仍将“好像”问题上有赏金
以下是我自己注释的代码。 特别是,对于每个 function 的前 10-14 行,我有很多样板代码,可以根据用户是否登录、是否已填写此信息、是否有 session 信息等来重定向用户。
# in model.py
choices = ([(x,str(x)) for x in range(1970,2015)])
choices.reverse()
class UserProfile(models.Model):
"""
Fields are user, network, location, graduation, headline, and position.
user is a ForeignKey, unique = True (OneToOne). network is a ForeignKey.
loation, graduation, headline, and position are optional.
"""
user = models.ForeignKey(User, unique=True)
network = models.ForeignKey(Network)
location = models.CharField(max_length=100, blank=True)
graduation = models.CharField(max_length=100, blank=True, choices=choices)
headline = models.CharField(max_length=100, blank=True)
positions = models.ManyToManyField(Position, blank=True)
avatar = models.ImageField(upload_to='images/%Y/%m/%d', blank=True, default='default_profile_picture.jpg')
# if the user has already filled out the 'getting started info', set boolean=True
getting_started_boolean = models.BooleanField(default=False)
一般上下文:用户注册后,我给他们两个 session 变量:
request.session['location'] = get_location_function(request)
request.session['username'] = new_user # this is an email address
用户注册后,他们将被重定向到 getting_started 页面。
第一页:
# in views.py
def getting_started_info(request, positions=[]):
"""
This is the first of two pages for the user to
add additional info after they have registrered.
There is no auto log-in after the user registers,
so the individiaul is an 'inactive user' until he
clicks the activation link in his email.
"""
location = request.session.get('location')
if request.user.is_authenticated():
username = request.user.username # first see if the user is logged in
user = User.objects.get(email=username) # if so, get the user object
if user.get_profile().getting_started_boolean:
return redirect('/home/') # redirect to User home if user has already filled out page
else:
pass
else:
username = request.session.get('username', False) # if not logged in, see if session info exists from registration
if not username:
return redirect('/account/login') # if no session info, redirect to login page
else:
user = User.objects.get(email=username)
if request.method == 'POST':
if 'Next Step' in request.POST.values(): # do custom processing on this form
profile = UserProfile.objects.get(user=user)
profile.location = request.POST.get('location')
populate_positions = []
for position in positions:
populate_positions.append(Position.objects.get(label=position))
profile.positions = request.POST.get('position')
profile.headline = request.POST.get('headline')
profile.graduation = request.POST.get('graduation')
profile.save()
return redirect('/account/gettingstarted/add_pic/')
else:
form = GettingStartedForm(initial={'location': location})
return render_to_response('registration/getting_started_info1.html', {'form':form, 'positions': positions,}, context_instance=RequestContext(request))
第二页:
def getting_started_pic(request):
"""
Second page of user entering info before first login.
This is where a user uploads a photo.
After this page has been finished, set getting_started_boolean = True,
so user will be redirected if hits this page again.
"""
if request.user.is_authenticated():
username = request.user.username
user = User.objects.get(email=username)
if user.get_profile().getting_started_boolean:
return redirect('/home/')
else:
pass
else:
username = request.session.get('username', False)
if not username:
return redirect('/account/login')
else:
user = User.objects.get(email=username)
try:
profile = UserProfile.objects.get(user=User.objects.get(email=username)) # get the profile to display the user's picture
except UserProfile.DoesNotExist: # if no profile exists, redirect to login
return redirect('/account/login') # this is a repetition of "return redirect('/account/login/')" above
if request.method == 'POST':
if 'upload' in request.POST.keys():
form = ProfilePictureForm(request.POST, request.FILES, instance = profile)
if form.is_valid():
if UserProfile.objects.get(user=user).avatar != 'default_profile_picture.jpg': # if the user has an old avatar image
UserProfile.objects.get(user=user).avatar.delete() # delete the image file unless it is the default image
object = form.save(commit=False)
try:
t = handle_uploaded_image(request.FILES['avatar']) # do processing on the image to make a thumbnail
object.avatar.save(t[0],t[1])
except KeyError:
object.save()
return render_to_response('registration/getting_started_pic.html', {'form': form, 'profile': profile,}, context_instance=RequestContext(request))
if 'finish' in request.POST.keys():
UserProfile.objects.filter(user=user).update(getting_started_boolean='True') # now add boolean = True so the user won't hit this page again
return redirect('/account/gettingstarted/check_email/')
else:
form = ProfilePictureForm()
return render_to_response('registration/getting_started_pic.html', {'form': form, 'profile': profile,}, context_instance=RequestContext(request))
第三页:
def check_email(request):
"""
End of getting started. Will redirect to user home
if activation link has been clicked. Otherwise, will
allow user to have activation link re-sent.
"""
if request.user.is_authenticated(): # if the user has already clicked his activation link, redirect to User home
return redirect('/home/')
else: # if the user is not logged in, load this page
resend_msg=''
user = email = request.session.get('username')
if not email:
return redirect('/account/login/')
if Site._meta.installed:
site = Site.objects.get_current()
else:
site = RequestSite(request)
if request.method == 'POST':
RegistrationProfile.objects.resend_activation(email, site)
resend_msg = 'An activation email has been resent to %s' %(email)
return render_to_response('registration/getting_started_check_email.html', {'email':email, 'resend_msg':resend_msg}, context_instance=RequestContext(request))
return render_to_response('registration/getting_started_check_email.html', {'email':email, 'resend_msg':resend_msg}, context_instance=RequestContext(request))
我会做一些不同的事情:
在你的 models.py 中,你使用你的graduation
字段的choices
参数,但不要在 model 上定义选择,并且不要使用通常用于表示 Python 中的常量的所有大写字母。 这会更好:
class UserProfile(models.Model): ... CHOICES = [('choice1', 'Choice 1'), ('choice2', 'Choice2')] graduation = models.CharField(max_length=100, blank=True, choices=CHOICES)
您在avatar
字段上使用可选的default
。 在您看来,使用blank=True
会更有意义,它实际上会使您的逻辑复杂化:
if UserProfile.objects.get(user=user).avatar.= 'default_profile_picture:jpg'. UserProfile.objects.get(user=user).avatar.delete()
相反,最好只处理模板中没有头像的问题。
在您的getting_started_info
视图中, positions
的默认参数是可变的 object, positions=[]
。 这让我总体上感到畏缩,尽管在你的事业中它不会给你带来任何问题,因为它从未发生过变异。 避免使用可变的 object 作为 python 中的默认参数是一个好主意,因为function 定义只评估一次。 最好避免这种情况。
>>> def foo(l=[]): ...: l.append(1)...: return l...: >>> foo() <<< [1] >>> foo() <<< [1, 1] >>> foo() <<< [1, 1, 1]
你做else: pass
,这是多余的,完全删除“else”。
if user.get_profile().getting_started_boolean: return redirect('/home/') # else: # pass
如果我以后有机会,我会 go 解决我对您拥有事物方式的其他一些问题。
(我还没有阅读你所有的代码,但我给出了一些一般性的建议,以使代码看起来更好)
我相信django-annoying的 render_to 装饰器使代码更易于阅读和编写。 (重定向仍然有效):
@render_to('registration/getting_started_info1.html')
def getting_started_info(request, positions=[]):
...
return {'form':form, 'positions': positions}
@render_to('registration/getting_started_pic.html')
def getting_started_pic(request):
...
return {'form': form, 'profile': profile}
@render_to('registration/getting_started_check_email.html')
def check_email(request):
...
return {'email':email, 'resend_msg':resend_msg}
在以下块中:
if request.user.is_authenticated():
return redirect('/home/')
else:
foo()
bar()
else
并不是真正需要的。 由于 return 语句,如果用户通过了身份验证, foo()
将不会被执行。 您可以通过删除它来防止过度缩进。
if request.user.is_authenticated():
return redirect('/home/')
foo()
bar()
我最初尝试使用 django.contrib.formtools.wizard 复制您的注册过程的行为,但考虑到您的过程中只有两个步骤,其中一个只是选择图像,它变得过于复杂。 如果您打算保留多步骤注册过程,我强烈建议您查看表单向导解决方案。 这意味着基础设施负责跨请求携带 state,您需要做的就是定义一系列 forms。
无论如何,我选择将您的整个过程简化为一个步骤。 使用基本的 model 表单,我们可以简单地在一页上捕获您需要的所有 UserProfile 信息,只需要很少的代码。
我还使用了 Django 1.3 中引入的基于类的视图。 它使样板代码(例如您在每个 function 顶部检查您正在执行的过程)变得更好管理,但代价是更多的前期复杂性。 但是,一旦您了解它们,它们对于许多用例来说都很棒。 好的,所以; 上代码。
# in models.py
graduation_choices = ([(x,str(x)) for x in range(1970,2015)])
graduation_choices.reverse()
class UserProfile(models.Model):
# usually you want null=True if blank=True. blank allows empty forms in admin, but will
# get a database error when trying to save the instance, because null is not allowed
user = models.OneToOneField(User) # OneToOneField is more explicit
network = models.ForeignKey(Network)
location = models.CharField(max_length=100, blank=True, null=True)
graduation = models.CharField(max_length=100, blank=True, null=True, choices=graduation_choices)
headline = models.CharField(max_length=100, blank=True, null=True)
positions = models.ManyToManyField(Position, blank=True)
avatar = models.ImageField(upload_to='images/%Y/%m/%d', blank=True, null=True)
def get_avatar_path(self):
if self.avatar is None:
return 'images/default_profile_picture.jpg'
return self.avatar.name
def is_complete(self):
""" Determine if getting started is complete without requiring a field. Change this method appropriately """
if self.location is None and self.graduation is None and self.headline is None:
return False
return True
我偷了这个答案的一部分来处理默认图像位置,因为这是非常好的建议。 将“要渲染的图片”留给模板和 model。 此外,在 model 上定义一个可以回答“已完成?”的方法问题,而不是尽可能定义另一个字段。 使过程更容易。
# forms.py
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
widgets = {
'user': forms.HiddenInput() # initial data MUST be used to assign this
}
基于 UserProfile object 的简单 ModelForm。 这将确保 model 的所有字段都暴露给一个表单,并且可以原子地保存所有内容。 这就是我主要偏离您的方法的方式。 与其使用几个 forms,只需一个即可。 我认为这也是一种更好的用户体验,特别是因为根本没有很多领域。 当用户想要修改他们的信息时,您还可以重复使用这个确切的表单。
# in views.py - using class based views available from django 1.3 onward
class SignupMixin(View):
""" If included within another view, will validate the user has completed
the getting started page, and redirects to the profile page if incomplete
"""
def dispatch(self, request, *args, **kwargs):
user = request.user
if user.is_authenticated() and not user.get_profile().is_complete()
return HttpResponseRedirect('/profile/')
return super(SignupMixin, self).dispatch(request, *args, **kwargs)
class CheckEmailMixin(View):
""" If included within another view, will validate the user is active,
and will redirect to the re-send confirmation email URL if not.
"""
def dispatch(self, request, *args, **kwargs):
user = request.user
if user.is_authenticated() and not user.is_active
return HttpResponseRedirect('/confirm/')
return super(CheckEmailMixin, self).dispatch(request, *args, **kwargs)
class UserProfileFormView(FormView, ModelFormMixin):
""" Responsible for displaying and validating that the form was
saved successfully. Notice that it sets the User automatically within the form """
form_class = UserProfileForm
template_name = 'registration/profile.html' # whatever your template is...
success_url = '/home/'
def get_initial(self):
return { 'user': self.request.user }
class HomeView(TemplateView, SignupMixin, CheckEmailMixin):
""" Simply displays a template, but will redirect to /profile/ or /confirm/
if the user hasn't completed their profile or confirmed their address """
template_name = 'home/index.html'
这些视图可能是最复杂的部分,但我觉得比大量的意大利面条视图 function 代码更容易理解。 我已经简要地记录了这些函数,所以它应该使它更容易理解。 唯一剩下的就是将您的 URL 连接到这些视图类。
# urls.py
urlpatterns = patterns('',
url(r'^home/$', HomeView.as_view(), name='home'),
url(r'^profile/$', UserProfileFormView.as_view(), name='profile'),
url(r'^confirm/$', HomeView.as_view(template_name='checkemail.html'), name='checkemail'),
)
现在这都是未经测试的代码,因此可能需要调整才能开始工作,并集成到您的特定站点中。 此外,它完全脱离了您的多步骤流程。 在许多许多领域的情况下,多步骤过程会很好。但是一个单独的页面只是做头像对我来说似乎有点极端。 希望无论您采用哪种方式 go,这都会有所帮助。
关于基于 class 的视图的一些链接:
我还想提一些关于您的代码的一般情况。 例如你有这个:
populate_positions = []
for position in positions:
populate_positions.append(Position.objects.get(label=position))
可以用这个代替:
populate_positions = Position.objects.filter(label__in=positions)
前者将为每个 position 命中数据库。 后者在评估时将执行单个查询。
还;
if request.user.is_authenticated():
username = request.user.username
user = User.objects.get(email=username)
以上是多余的。 您已经可以访问用户 object,然后再次尝试获取它。
user = request.user
完毕。
顺便说一句,如果你想使用 email 地址作为用户名,你会遇到问题。 数据库最多只能接受 30 个字符(这是用户 model 在 contrib.auth 中的写入方式)。 阅读其中一些关于此线程的评论,讨论一些陷阱。
在check_email
中,最后两行完全相同。 您可以删除第一个。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.