改善Python / Django视图代码 [英] Improving Python/django view code

查看:65
本文介绍了改善Python / Django视图代码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对Python / Django和一般编程非常陌生。使用我的编程袋中有限的工具,我为用户注册后编写了三种视图功能:允许用户在激活帐户之前添加信息并上传缩略图。



<我已经发布了到目前为止编写的代码,以便比我更有经验的人可以向我展示如何改进代码。毫无疑问,这是带有新手所有标记的原始代码,但是我从编写代码中学到了最好的知识-看到改进代码的方式并学习新工具-并重写它。



我知道该问题的答案将花费大量时间。因此, 我将奖励该问题200点奖励。 因此,我只允许在问题发布两天后添加奖励。在星期二为这个问题添加赏金(可立即添加)。请注意,由于要等到我发布赏金后才选择答案,所以在添加赏金之前提供的答案仍然是好像有问题的赏金



以下是我的自我注释代码。特别是,对于每个函数的前10-14行,我有很多样板代码,可根据用户是否已登录,是否已填写此信息,是否具有会话信息等来重定向用户。 / p>

 #在model.py 
选择=([[x,str(x))对于x范围(1970 ,2015)])
choices.reverse()

class UserProfile(models.Model):

字段是用户,网络,位置,毕业,标题和位置。
用户是一个ForeignKey,唯一= True(OneToOne)。网络是一个ForeignKey。
位置,毕业程度,标题和位置是可选的。

用户=模型.ForeignKey(用户,唯一=真)
网络=模型.ForeignKey(网络)
位置=模型.CharField(max_length = 100,blank = True)
毕业= models.CharField(max_length = 100,blank = True,choices = choices)
headline = models.CharField(max_length = 100,blank = True)
position = models.ManyToManyField(Position ,blank = True)
头像= models.ImageField(upload_to ='images /%Y /%m /%d',blank = True,default ='default_profile_picture.jpg')
#如果用户已经填写了入门信​​息,设置boolean = True
Getting_started_boolean = models.BooleanField(default = False)

一般情况:
用户注册后,我给他们两个会话变量:

  request.session ['location'] = get_location_function(request)
request.session ['username'] = new_user#这是一个电子邮件地址

用户注册后,他们将被重定向到入门页面。



第一页:

 #in views.py 

def get_started_info(request,position = []):

这是用户两页中的第一页,以便
在他们注册后添加其他信息。
用户注册后没有自动登录,
因此个人是一个非活动用户,直到他
单击其电子邮件中的激活链接为止。

位置= request.session.get('位置')
如果request.user.is_authenticated():
用户名= request.user.username#首先查看如果用户已登录
user = User.objects.get(email = username)#如果是,则获取用户对象
如果user.get_profile()。getting_started_boolean:
return redirect( '/ home /')#如果用户已经填写了页面
,则重定向到用户主目录
传递
否则:
用户名= request.session.get('用户名' ,False)#如果未登录,请查看注册是否存在会话信息
如果不是用户名:
return redirect('/ account / login')#如果没有会话信息,请重定向至登录页面
else:
用户= User.objects.get(电子邮件=用户名)
如果request.method =='POST':
如果要求中的'Next Step' est.POST.values():#在此表单上进行自定义处理
profile = UserProfile.objects.get(user = user)
profile.location = request.POST.get('location')
populate_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 /')
其他:
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):

用户在首次登录前输入信息的第二页。
这是用户上传照片的地方。
在此页面结束后,设置Getting_started_boolean = True,
,以便在再次单击此页面时将重定向用户。

如果request.user.is_authenticated():
用户名= request.user.username
用户= User.objects.get(电子邮件=用户名)
如果user.get_profile()。getting_started_boolean:
返回重定向('/ home /')
否则:
传递
否则:
用户名= request.session。 get('username',False)
如果不是用户名:
return redirect('/ account / login')
else:
user = User.objects.get(email =用户名)
尝试:
个人资料= UserProfile.objects.get(user = User.objects.get(email = username))#获取个人资料以显示用户图片
,UserProfile.DoesNotExist除外:#如果没有配置文件,则重定向到登录
return redirect('/ account / login')#这是 return redirect('/ a ccount / login /')如果request.method =='POST'在
上方:
如果request.POST.keys()中的'upload':
form = ProfilePictureForm(request.POST ,request.FILES,instance = profile)
如果form.is_valid():
如果UserProfile.objects.get(user = user).avatar!='default_profile_picture.jpg':#如果用户拥有旧的头像图片
UserProfile.objects.get(user = user).avatar.delete()#删除图片文件,除非它是默认图片
object = form.save(commit = False)
尝试:
t = handle_uploaded_image(request.FILES ['avatar'])#对图像进行处理以制作缩略图
object.avatar.save(t [0],t [1] )
,但KeyError:
object.save()
return render_to_response('registration / getting_started_pic.html',{'form':form,'profile':profile,},context_instance = RequestCont ext(request))
如果request.POST.keys()中的'finish'是:
UserProfile.objects.filter(user = user).update(getting_started_boolean ='True')#现在添加boolean =正确,因此用户不会再访问此页面
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):

入门结束。如果单击了激活链接,将重定向到用户家
。否则,
将允许用户重新发送激活链接。

如果request.user.is_authenticated():#如果用户已经单击了他的激活链接,则重定向到User home
return redirect('/ home /')
else:#如果用户未登录,请加载此页面
resend_msg =''
user = email = request.session.get('username')
如果不是电子邮件:
return redirect('/ account / login /')
如果安装了Site._meta .:
site = Site.objects.get_current()
else:
site = RequestSite(请求)
(如果request.method =='POST':
RegistrationProfile.objects.resend_activation(电子邮件,站点)
resend_msg ='激活电子邮件已重新发送到%s' %(电子邮件)
返回render_to_response('registration / getting_started_check_email.html',{'email':email,'resend_msg':resend_msg},context_instance = RequestContext(request))
返回render_to_re sponse('registration / getting_started_check_email.html',{'email':email,'resend_msg':resend_msg},context_instance = RequestContext(request))


解决方案

我最初尝试使用django.contrib.formtools.wizard复制您的注册过程的行为,但是考虑到那里,它变得太复杂了只是过程中的两个步骤,其中之一就是选择一张图片。如果您打算保留多步骤注册过程,我强烈建议您考虑使用表单向导解决方案。这将意味着基础架构负责在请求之间传递状态,而您所需要做的就是定义一系列表格。



无论如何,我选择简化您的整个过程只需一步。使用基本的模型形式,我们可以用很少的代码简单地捕获一页上需要的所有UserProfile信息。



基于类的视图,在Django 1.3中引入。它使样板代码(例如,您在每个功能的顶部检查要完成的过程)更易于管理,但代价是前期的复杂性更高。但是,一旦您了解了它们,它们对于许多用例来说都是很棒的。好吧

 #in models.py 

Graduation_choices =([[x,str (x))对于范围内的x(1970,2015)])
Graduate_choices.reverse()

类UserProfile(models.Model):
#通常您想要null =如果空白= True,则为True。 blank允许在admin中使用空表格,但
#尝试保存实例时会出现数据库错误,因为不允许null
user = models.OneToOneField(User)#OneToOneField更显式
网络=模型.ForeignKey(网络)
位置=模型.CharField(max_length = 100,空白=真,null =真)
毕业=模型.CharField(max_length = 100,空白=真,null = True,options = graduation_choices)
标题= models.CharField(max_length = 100,blank = True,null = True)
position = models.ManyToManyField(Position,blank = True)
头像= models.ImageField(upload_to ='images /%Y /%m /%d',blank = True,null = True)

def get_avatar_path(self):
如果self.avatar为None:
返回'images / default_profile_picture.jpg'
返回self.avatar.name

def is_complete(self):
确定是否开始不需要字段即可完成。请更改此方法ap如果self.location为None且self.graduation为None且self.headline为None,则适当地

返回False
返回True

我偷了此答案的一部分用于处理默认图片位置,因为这是非常好的建议。将要渲染的图片留给模板和模型。另外,在模型上定义一个可以回答已完成?问题的方法,而不是尽可能定义另一个字段。

 #form.py 

类UserProfileForm(forms.ModelForm):
类元:
模型= UserProfile
小部件= {
'user':form.HiddenInput()#必须使用初始数据来分配此
}

基于UserProfile对象的简单ModelForm。这将确保模型的所有字段都暴露于表单,并且所有内容都可以原子保存。这就是我主要偏离您的方法的方式。不用使用多种形式,只需一种即可。我认为这也是一种更好的用户体验,尤其是因为根本没有太多的领域。您还可以在用户想要修改其信息时重复使用此精确形式。

 #in views.py-使用基于类django 1.3及更高版本中可用的视图

类SignupMixin(View):
如果包含在另一个视图中,将验证用户已完成
入门页面,并重定向如果不完整

def dispatch(self,request,* args,** kwargs)到个人资料页面:
user = request.user
if user.is_authenticated( )而不是user.get_profile()。is_complete()
返回HttpResponseRedirect('/ profile /')
返回super(SignupMixin,self).dispatch(request,* args,** kwargs)

类CheckEmailMixin(View):
如果包含在另一个视图中,将验证用户是否处于活动状态,
并将重定向到重新发送确认电子邮件的URL(如果没有)。


def派遣(自身,请求,* args,** kwargs):
user = requ est.user
如果user.is_authenticated()而不是user.is_active
返回HttpResponseRedirect('/ confirm /')
返回super(CheckEmailMixin,self).dispatch(request,* args, ** kwargs)

类UserProfileFormView(FormView,ModelFormMixin):
负责显示和验证表单是否已成功保存
。请注意,它会自动以形式设置用户。

form_class = UserProfileForm
template_name ='registration / profile.html'#无论您的模板是什么...
success_url ='/ home /'

def get_initial(self):
return {'user':self.request.user}

class HomeView(TemplateView,SignupMixin ,CheckEmailMixin):
仅显示模板,但是如果用户尚未完成其个人资料或未确认其地址,则会重定向到/ profile /或/ confirm /

template_name ='home / index.html'

这些视图可能是最复杂的部分,但是我觉得比起意大利面条式视图函数代码要容易理解得多。我已经简短地内联记录了这些函数,因此应该使它更易于理解。剩下的就是将URL连接到这些视图类。 / p>

 #urls.py 

urlpatterns = patte rns('',

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'),

现在这都是未经测试的代码,因此可能需要进行调整才能工作并进行集成进入您的特定网站。而且,它完全偏离您的多步骤过程。在很多领域的情况下,多步过程会很好。.但是对我来说,单独做化身的页面似乎有点极端。



一些有关基于类的视图的链接:



API参考

主题简介



我也想提一提关于您的代码的一般情况。例如,您有以下内容:

  populate_positions = [] 
表示仓位:
populate_positions.append (Position.objects.get(label = position))

可以用以下替换:

  populate_positions = Position.objects.filter(label__in = positions)

前者将在每个位置击中DB。



也;

 如果request.user.is_authenticated():
用户名= request.user.username
用户= User.objects.get(email = username)

以上是多余的。您已经可以访问用户对象,然后尝试再次获取它。

  user = request.user 

完成。



顺便说一句,如果您想要使用电子邮件地址作为用户名,您将遇到问题。该数据库最多只能接受30个字符(这是在contrib.auth中写入用户模型的方式)。在此线程讨论了一些陷阱。


I am very new to Python/Django and programming in general. With the limited tools in my programming bag, I have written three views functions for after a user registers: it allows the user to add information and upload a thumbnail before activating his account.

I have posted the code that I have written so far so that someone with far more experience than I have can show me how to improve the code. No doubt this is crude code with all the marks of a novice, but I learn best from writing code -- seeing ways to improve it and learning new tools -- and rewriting it.

I know that the answer to this question will take a considerable amount of time. Therefore, I will be awarding a 200 point bounty to this question. SO will only allow me to add a bounty two days after a question has been posted, so I will be adding a bounty to this question on Tuesday (as soon as it's available to add). Please note that since I won't be selecting an answer until after I have posted a bounty, answers that are provided before the bounty has been added will still be 'as if' there is a bounty on the question

The following is my self-commented code. In particular, I have a lot of boilerplate code for the first 10-14 lines of each function to redirect a user based upon whether he is logged in, if he has already filled out this info, if he has session info, etc.

# 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) 

General context: after a user has registered, I am giving them two session variables:

    request.session['location'] = get_location_function(request)
    request.session['username'] = new_user   # this is an email address

After a user has registered, they are re-directed to the getting_started pages.

First page:

# 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))

Second page:

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))

Third page:

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))

解决方案

I originally tried to replicate the behaviour of your signup process using django.contrib.formtools.wizard, but it was becoming far too complicated, considering there are only two steps in your process, and one of them is simply selecting an image. I would highly advise looking at a form-wizard solution if you intend to keep the multi-step signup process though. It will mean the infrastructure takes care of carrying state across requests, and all you need to do is define a series of forms.

Anyway, I've opted to simplify your whole process to one step. Using a basic model form, we are able to simply capture ALL of the UserProfile information you need on one page, with very very little code.

I've also gone with class-based-views, introduced in Django 1.3. It makes boilerplate code (such as your check at the top of each function for what process you're up to) much nicer to manage, at the cost of more upfront complexity. Once you understand them though, they are fantastic for a lot of use cases. Ok, so; on to the code.

# 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

I stole a piece of this answer for handling the default image location as it was very good advice. Leave the 'which picture to render' up to the template and the model. Also, define a method on the model which can answer the 'completed?' question, rather than defining another field if possible. Makes the process easier.

# forms.py

class UserProfileForm(forms.ModelForm):
    class Meta:
        model = UserProfile
        widgets = {
            'user': forms.HiddenInput() # initial data MUST be used to assign this
        }

A simple ModelForm based on the UserProfile object. This will ensure that all fields of the model are exposed to a form, and everything can be saved atomically. This is how I've mainly deviated from your method. Instead of using several forms, just one will do. I think this is a nicer user experience also, especially since there aren't very many fields at all. You can also reuse this exact form for when a user wants to modify their information.

# 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'

These views will probably be the most complicated part, but I feel are much easier to understand than reams of spaghetti view function code. I've documented the functions briefly inline, so it should make it slightly easier to understand. The only thing left is to wire up your URLs to these view classes.

# 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'),
)

Now this is all untested code, so it may need tweaks to get working, and to integrate into your particular site. Also, it completely departs from your multi-step process. The multi-step process would be nice in the case of many many many fields.. but a separate page JUST to do the avatar seems a bit extreme to me. Hopefully, whichever way you go, this helps.

Some links regarding class based views:

API Reference
Topic Introduction

I also wanted to mention a few things about your code in general. For instance you have this:

populate_positions = []
for position in positions:
    populate_positions.append(Position.objects.get(label=position))

Which could be replaced with this:

populate_positions = Position.objects.filter(label__in=positions)

The former will hit the DB for every position. The latter will do a single query when evaluated.

Also;

if request.user.is_authenticated():
    username = request.user.username                      
    user = User.objects.get(email=username)

The above is redundant. You've got access to the user object already, and then trying to fetch it again.

user = request.user

Done.

By the way, if you want to use email addresses as a username, you will have problems. The database will only accept a maximum of 30 characters (it is how the User model is writtin in contrib.auth). Read some of them comments on this thread that discuss some of the pitfalls.

这篇关于改善Python / Django视图代码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆