简体   繁体   English

改进 Python/django 视图代码

[英]Improving Python/django view code

I am very new to Python/Django and programming in general.我对 Python/Django 和一般编程非常陌生。 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.因此,我将为此问题奖励 200 分。 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.特别是,对于每个 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) 

General context: after a user has registered, I am giving them two session variables:一般上下文:用户注册后,我给他们两个 session 变量:

    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.用户注册后,他们将被重定向到 getting_started 页面。

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

A few things I'd do differently:我会做一些不同的事情:

  1. In your models.py you use the choices argument for your graduation field, but don't define the choices on the model, and don't use all caps which is usually used to denote a constant in Python.在你的 models.py 中,你使用你的graduation字段的choices参数,但不要在 model 上定义选择,并且不要使用通常用于表示 Python 中的常量的所有大写字母。 This would be better:这会更好:

     class UserProfile(models.Model): ... CHOICES = [('choice1', 'Choice 1'), ('choice2', 'Choice2')] graduation = models.CharField(max_length=100, blank=True, choices=CHOICES)
  2. You use the optional default on your avatar field.您在avatar字段上使用可选的default It would make more sense to use blank=True , it actually complicates your logic later on, in your view:在您看来,使用blank=True会更有意义,它实际上会使您的逻辑复杂化:

     if UserProfile.objects.get(user=user).avatar.= 'default_profile_picture:jpg'. UserProfile.objects.get(user=user).avatar.delete()

    Instead it's probably better to just deal with the absence of an avatar in your template.相反,最好只处理模板中没有头像的问题。

  3. In your getting_started_info view, the default paramater for positions is a mutable object, positions=[] .在您的getting_started_info视图中, positions的默认参数是可变的 object, positions=[] This makes me cringe in general, although in your cause it won't cause you any issues since it's never mutated.这让我总体上感到畏缩,尽管在你的事业中它不会给你带来任何问题,因为它从未发生过变异。 It's a good idea to avoid using a mutable object as a default parameter in python, because function definitions are only evaluated once .避免使用可变的 object 作为 python 中的默认参数是一个好主意,因为function 定义只评估一次 It's best to avoid this.最好避免这种情况。

     >>> def foo(l=[]): ...: l.append(1)...: return l...: >>> foo() <<< [1] >>> foo() <<< [1, 1] >>> foo() <<< [1, 1, 1]
  4. You do else: pass , this is redundant, remove the `else' completely.你做else: pass ,这是多余的,完全删除“else”。

     if user.get_profile().getting_started_boolean: return redirect('/home/') # else: # pass

If I get a chance later, I'll go over a few more issues I have with the way you have things.如果我以后有机会,我会 go 解决我对您拥有事物方式的其他一些问题。

(I did not read all your code yet, but I am giving some general advices to make the code look better) (我还没有阅读你所有的代码,但我给出了一些一般性的建议,以使代码看起来更好)

I believe django-annoying 's render_to decorator makes code easier to read and write.我相信django-annoying的 render_to 装饰器使代码更易于阅读和编写。 (The redirects are still working): (重定向仍然有效):

@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}

In the following block:在以下块中:

if request.user.is_authenticated():
    return redirect('/home/')
else:
    foo()
    bar()

the else is not really needed. else并不是真正需要的。 Because of the return statement, foo() will not be executed if the user is authenticated.由于 return 语句,如果用户通过了身份验证, foo()将不会被执行。 You can prevent over-indentation by removing it.您可以通过删除它来防止过度缩进。

if request.user.is_authenticated():
    return redirect('/home/')

foo()
bar()

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.我最初尝试使用 django.contrib.formtools.wizard 复制您的注册过程的行为,但考虑到您的过程中只有两个步骤,其中一个只是选择图像,它变得过于复杂。 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.这意味着基础设施负责跨请求携带 state,您需要做的就是定义一系列 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.使用基本的 model 表单,我们可以简单地在一页上捕获您需要的所有 UserProfile 信息,只需要很少的代码。

I've also gone with class-based-views, introduced in Django 1.3.我还使用了 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.它使样板代码(例如您在每个 function 顶部检查您正在执行的过程)变得更好管理,但代价是更多的前期复杂性。 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.将“要渲染的图片”留给模板和 model。 Also, define a method on the model which can answer the 'completed?'此外,在 model 上定义一个可以回答“已完成?”的方法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.基于 UserProfile object 的简单 ModelForm。 This will ensure that all fields of the model are exposed to a form, and everything can be saved atomically.这将确保 model 的所有字段都暴露给一个表单,并且可以原子地保存所有内容。 This is how I've mainly deviated from your method.这就是我主要偏离您的方法的方式。 Instead of using several forms, just one will do.与其使用几个 forms,只需一个即可。 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.这些视图可能是最复杂的部分,但我觉得比大量的意大利面条视图 function 代码更容易理解。 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.唯一剩下的就是将您的 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'),
)

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.希望无论您采用哪种方式 go,这都会有所帮助。

Some links regarding class based views:关于基于 class 的视图的一些链接:

API Reference API 参考
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.前者将为每个 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.您已经可以访问用户 object,然后再次尝试获取它。

user = request.user

Done.完毕。

By the way, if you want to use email addresses as a username, you will have problems.顺便说一句,如果你想使用 email 地址作为用户名,你会遇到问题。 The database will only accept a maximum of 30 characters (it is how the User model is writtin in contrib.auth).数据库最多只能接受 30 个字符(这是用户 model 在 contrib.auth 中的写入方式)。 Read some of them comments on this thread that discuss some of the pitfalls.阅读其中一些关于此线程的评论,讨论一些陷阱。

In check_email , the last two lines are the exact same.check_email中,最后两行完全相同。 You can delete the first one.您可以删除第一个。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM