Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Lesson 20 - Users Sign-up and Permissions

Lesson 20 - Users Sign-up and Permissions

Dana Spiegel

December 18, 2012
Tweet

More Decks by Dana Spiegel

Other Decks in Technology

Transcript

  1. Quick Review • User management • SMTP email configuration •

    Sites application • User management templates 2
  2. Protecting Views by Requiring Login • To configure a view

    to require a logged in user, use the @login_required decorator • This decorator will redirect a user to the login page if they aren’t authenticated 3 from django.contrib.auth.decorators import login_required ... @login_required def game_delete(request, game_id): ...
  3. Display User Information in Navbar • Always provide a login

    link to users in a prominent location • Once user is logged in, always show a menu or header that provides • A display that the user is logged in • A way to access user profile • A way to change user’s password • A way to log out • Users have come to expect that this information and functionality is available at the top of every page • Include identifying information about the user (email or password) so that they know which account they are logged in as 4
  4. Example: User Information in Navbar 5 <ul class="nav pull-right"> {%

    if user.is_authenticated %} <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown"> <i class="icon-user"></i> <b class="caret"></b> </a> <ul class="dropdown-menu"> <li> <a href="{% url user_edit %}"> <div> <strong>{{ user.get_full_name }}</strong><br/> <small class="muted">Edit Profile</small> </div> </a> </li> <li class="divider"></li> <li><a class="navbar-link" href="{% url password_change %}"> Change Password</a></li> <li class="divider"></li> <li><a class="navbar-link" href="{% url logout %}"> Logout</a></li> </ul> </li> {% else %} <li> <a class="navbar-link" href="{% url login %}">Login</a> </li> {% endif %} </ul>
  5. User Sessions • A session is a temporary, long-term collection

    of information that contains the state of a set of network interactions • They are usually set up during the initial interaction and expire after some period of time • Sessions are expected to live for longer than a single interaction in order to accelerate subsequent interactions by reducing setup time and the amount of information that must be gathered in order to perform actions • Contain information about the current state of a series of interactions, and are used primarily when the communication protocol is stateless • Identified using a key or ID that is passed back and forth between the client and server to enable the server to identify the client identity • For web interactions, sessions are maintained on the server in some form of simple and fast storage mechanism • Database, memory cache, key/value storage, disk 6
  6. User Sessions (cont’d.) • For interaction with a web application,

    a browser will store the session key or identifier in a cookie and send the session ID with every HTTP request • On the server, the session keeps track of the user identity and access credentials to enable actions to be authorized without requiring login for every interaction • Web sessions can contain additional data, but its best to keep as little as possible in the session on the server since it needs to be stored and retrieved for every HTTP request • If the expiration time of a session is long enough, a user can come back to the web application at a later date and not have to log in • The longer the expiration time, the less secure the site, since an attacker only needs the session cookie (available on the user’s computer) in order to access the site as that user 7
  7. Django User Sessions • Default storage is in the database,

    but can be configured to use in- process memory, Memcached, local disk, or other storage locations • A Django session contains the user that was authenticated • If a user hasn’t authenticated, there will still be a session, with an AnonymousUser • When a session expires, the user must log in again • When a web browser makes an HTTP request, if it provides a session identifier via a cookie, Django will look up the session based on the session key, retrieve it, and reuse it • Django sessions require cookies in the web browser • The HTTPRequest object that is passed to a view contains both a user object (cached in the session) and the session data • Django provides an API for managing session data 8 request.session.get('foo', False) request.session['bar'] = 123 del request.session['baz']
  8. User Signup • Django provides a simple user sign-up form

    in auth application that can be used to build a user sign-up view • Significant differences from a standard ModelForm: • username is checked for existence prior to creating new user • password has 2 fields, to ensure user has entered it properly; Form checks to ensure fields are the same before saving • When creating User Signup view, ensure it isn’t protected by @login_required (for obvious reasons) • After successfully creating user, log them in • authenticate method takes username and password arguments and retrieves user instance • login method takes request and user arguments and sets the user as the authenticated user for the session • Be sure to redirect the user to a sensible page upon sign-up 9
  9. user_signup 10 def user_signup(request): if request.method == 'POST': form =

    forms.UserSignupForm(data=request.POST) if form.is_valid(): form.save() # log user in username = form.cleaned_data['username'] password = form.cleaned_data['password1'] user = authenticate(username=username, password=password) login(request, user) messages.success(request, u'Welcome to the Softball Tracker') return redirect('softball_home') else: form = forms.UserSignupForm() return TemplateResponse(request, 'softball/user_signup.html', { 'form': form, })
  10. User Profile Editing • Allowing a user to update their

    account information works just like any other ModelForm • Only requirement is that users should only ever be allowed to view and update their own account information • Instead of identifying the user ID in the URL, user identification should be handled in the view, using request.user • Update urls.py: • Create a UserForm, but limit the fields presented to the user: 11 from django.contrib.auth.models import User ... class UserForm(ModelForm): class Meta: model = User fields = ('email', 'first_name', 'last_name', ) ... url(r'^user/$', 'softball.views.user_edit', name='user_edit'), ...
  11. User Profile Editing (cont’d.) • In the user_edit view, get

    the user from request and instantiate the form using the user as the instance • Redirect the user to the user_edit view on success, including a message 12 @login_required def user_edit(request): user = request.user if request.method == 'POST': form = forms.UserForm(request.POST, instance=user) if form.is_valid(): form.save() messages.success(request, u'User profile updated') return redirect('user_edit') else: form = forms.UserForm(instance=user) return TemplateResponse(request, 'softball/user_edit.html', { 'user': user, 'form': form, })
  12. Permissions • Django auth application provides a simple permissions system

    • Permissions belong to Models • By default, each Model will have 3 permissions defined: add_<model>, change_<model>, delete_<model> • Permission names are always <application_name>.<permission_name> • Permissions can be checked by querying user • For views, use the @permission_required decorator (like @login_required) to ensure user has permission to access view • If action in a view must be protected, use user.has_perm • In template, perms context variable can be queried for the user’s permissions • Best practice: hide buttons for actions user is not allowed to use 13 user.has_perm('foo.add_bar') user.has_perm('foo.change_bar') user.has_perm('foo.delete_bar')
  13. Updated team_create 14 @login_required @permission_required('softball.add_team') def team_create(request): if request.method ==

    'POST': team_form = forms.TeamForm(data=request.POST) if team_form.is_valid(): team = team_form.save(commit=False) team.owned_by = request.user team.save() messages.success(request, 'Team {0} created'.format(team.name)) return redirect('team_edit', team_id=team.id) else: team_form = forms.TeamForm() return TemplateResponse(request, 'softball/team/create.html', { 'team_form': team_form, })
  14. Updated game/list.html 15 ... {% if perms.softball.add_game %} <div class="pagination

    pagination-left pagination-small"> <a class="btn" href="{% url game_create %}">Add Game</a> </div> {% endif %} ...
  15. Updated game/view.html 16 ... <p class="right"> {% if perms.softball.delete_game %}

    <a class="btn btn-danger" href="{% url game_delete game_id=game.id %}"> Delete Game</a> {% endif %} {% if perms.softball.change_game %} <a class="btn" href="{% url game_edit game_id=game.id %}">Edit Game</a> {% endif %} </p> ...
  16. Groups • Groups are also provided by auth application •

    Groups are collections of permissions to which a User can be assigned • They can be managed entirely through Django admin • Permissions can be assigned to Groups • Group membership can be selected for each User • Permissions assigned to a Group are inherited by the Users that are members of that group • When user.has_perm is used, it checks the permissions assigned to the user and those that are assigned to a User’s Groups 17
  17. Model Instance Ownership • To assign an instance of a

    Model to a User, add an owned_by field • This will require an update to database tables • To make this work properly requires changes to all create and edit views to assign request.user to owned_by field for instance being created or edited 18 owned_by = django.db.models.ForeignKey('auth.User', related_name='teams') ALTER TABLE softball_team ADD COLUMN "owned_by_id" INTEGER NOT NULL REFERENCES "auth_user" ("id"); ALTER TABLE softball_player ADD COLUMN "owned_by_id" INTEGER NOT NULL REFERENCES "auth_user" ("id"); ALTER TABLE softball_game ADD COLUMN "owned_by_id" INTEGER NOT NULL REFERENCES "auth_user" ("id"); ALTER TABLE softball_roster ADD COLUMN "owned_by_id" INTEGER NOT NULL REFERENCES "auth_user" ("id"); ALTER TABLE softball_statistic ADD COLUMN "owned_by_id" INTEGER NOT NULL REFERENCES "auth_user" ("id");
  18. Updated team_create 19 @login_required @permission_required('softball.add_team') def team_create(request): if request.method ==

    'POST': team_form = forms.TeamForm(data=request.POST, user=request.user) if team_form.is_valid(): team = team_form.save(commit=False) team.owned_by = request.user team.save() messages.success(request, 'Team {0} created'.format(team.name)) return redirect('team_edit', team_id=team.id) else: team_form = forms.TeamForm(user=request.user) return TemplateResponse(request, 'softball/team/create.html', { 'team_form': team_form, })
  19. Updated team_edit 20 @login_required @permission_required('softball.change_team') def team_edit(request, team_id): try: team

    = models.Team.objects.get(pk=team_id, owned_by=request.user) except models.Team.DoesNotExist: raise Http404 if request.method == 'POST': team_form = forms.TeamForm(data=request.POST, user=request.user, instance=team) player_formset = forms.TeamPlayerModelFormSet(data=request.POST, user=request.user, instance=team) if team_form.is_valid() and player_formset.is_valid(): team = team_form.save() messages.success(request, 'Team {0} updated'.format(team.name)) player_formset.save() return redirect('team_view', team_id=team.id) else: team_form = forms.TeamForm(request.user, instance=team) player_formset = forms.TeamPlayerModelFormSet(user=request.user, instance=team) return TemplateResponse(request, 'softball/team/edit.html', { 'team': team, 'record': team.record(), 'team_form': team_form, 'player_formset': player_formset, })
  20. Updating Forms to Support Users 21 • owned_by field shouldn’t

    be shown to users, since it is internal functionality • Make sure ModelForms exclude owned_by field • For ModelForms that enable editing of Foreign Key relationships, add user to __init__() method • Save user locally in form instance • Update queryset for fields to only allow selection of related objects that belong to the indicated user • Override save() method to assign owned_by if necessary • For FormSets, override ModelForm and FormSet used to support user argument
  21. Updated PlayerForm 22 class PlayerForm(ModelForm): error_css_class = 'text-error' required_css_class =

    'text-required' class Meta: model = models.Player exclude = ('owned_by', ) def __init__(self, user, *args, **kwargs): self.user = user super(PlayerForm, self).__init__(*args, **kwargs) self.fields['team'].queryset = self.user.teams.all() def save(self, commit=True): player = super(PlayerForm, self).save(commit=False) player.owned_by = self.user if commit: player.save() return player
  22. Updated TeamPlayerModelFormSet 23 class TeamPlayerForm(ModelForm): class Meta: model = models.Player

    exclude = ('owned_by', ) def __init__(self, user, *args, **kwargs): self.user = user # accept a Team to use for Player selection super(TeamPlayerForm, self).__init__(*args, **kwargs) def save(self, commit=True): player = super(TeamPlayerForm, self).save(commit=False) player.owned_by = self.user if commit: player.save() return player class TeamPlayerFormSet(BaseInlineFormSet): def __init__(self, user, *args, **kwargs): self.user = user super(TeamPlayerFormSet, self).__init__(*args, **kwargs) def _construct_form(self, index, **kwargs): # override _construct_form to add self.team to the StatisticForm upon # creation kwargs['user'] = self.user return super(TeamPlayerFormSet, self)._construct_form(index, **kwargs) TeamPlayerModelFormSet = inlineformset_factory( parent_model=models.Team, model=models.Player, formset=TeamPlayerFormSet, form=TeamPlayerForm, extra=20, max_num=20)