簡體   English   中英

改進 Python/django 視圖代碼

[英]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))

我會做一些不同的事情:

  1. 在你的 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)
  2. 您在avatar字段上使用可選的default 在您看來,使用blank=True會更有意義,它實際上會使您的邏輯復雜化:

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

    相反,最好只處理模板中沒有頭像的問題。

  3. 在您的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]
  4. 你做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 的視圖的一些鏈接:

API 參考
主題介紹

我還想提一些關於您的代碼的一般情況。 例如你有這個:

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM