Commits

James Bennett committed b0dbc4a

Initial Mercurial import

  • Participants

Comments (0)

Files changed (12)

+=========================
+django-profiles changelog
+=========================
+
+
+Version 0.1, 24 November 2007:
+------------------------------
+
+* Initial release.
+Thanks for downloading django-profiles.
+
+To install it, run the following command inside this directory:
+
+    python setup.py install
+
+Or if you'd prefer you can simply place the included ``profiles``
+directory somewhere on your Python path, or symlink to it from
+somewhere on your Python path; this is useful if you're working from a
+Subversion checkout.
+
+Note that this application requires Python 2.3 or later, and a recent
+Subversion checkout of Django. You can obtain Python from
+http://www.python.org/ and Django from http://www.djangoproject.com/.
+
+Copyright (c) 2007, James Bennett
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of the author nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+include INSTALL.txt
+include LICENSE.txt
+include MANIFEST.in
+include README.txt
+recursive-include docs *
+====================
+Django user profiles
+====================
+
+This is a fairly simple user-profile management application for
+Django_, designed to make the management of site-specific user
+profiles as painless as possible. It requires a recent Subversion
+checkout of Django, and provides a useful complement to
+`django-registration`_, but has no other dependencies.
+
+For installation instructions, see the file "INSTALL.txt" in this
+directory; for instructions on how to use this application, and on
+what it provides, see the file "overview.txt" in the "docs/"
+directory.
+
+
+.. _Django: http://www.djangoproject.com/
+.. _django-registration: http://code.google.com/p/django-registration/

docs/overview.txt

+====================
+Django user profiles
+====================
+
+
+This is a fairly simple user-profile management application for
+Django_, designed to make the process of creating, editing and viewing
+user profiles as painless as possible.
+
+.. _Django: http://www.djangoproject.com/
+
+
+Overview
+========
+
+In order to provide site-specific, per-user information in addition to
+what's stored by the ``User`` model in ``django.contrib.auth``, Django
+allows the use of a "user profile" model, which should be specified as
+the value of the ``AUTH_PROFILE_MODULE`` setting. If you're unfamiliar
+with this process, documentation is available in the "Profiles"
+section of `Chapter 12 of the Django book`_. This application assumes
+that you have created a profile model for use on your site, and that
+you have filled in the ``AUTH_PROFILE_MODULE`` setting appropriately.
+
+This application provides views which cover the primary aspects of
+using profiles:
+
+* Allowing users to create their profiles
+
+* Allowing users to edit their profiles
+
+* Allowing profiles to be publicly viewable
+
+Of these, the first two are the most important; the third -- publicly
+viewing user profiles -- is useful, but for sites which use profiles
+merely to store user preferences this can usually be omitted.
+
+
+.. _Chapter 12 of the Django book: http://www.djangobook.com/en/beta/chapter12/
+
+
+Basic use
+=========
+
+For default use, create a profile model for your site and specify the
+``AUTH_PROFILE_MODULE`` setting appropriately. Then add ``profiles``
+to your ``INSTALLED_APPS`` setting, create the appropriate templates
+and set up the URLs. For convenience in linking to profiles, your
+profile model should define a ``get_absolute_url()`` method which
+routes to the view ``profiles.views.profile_detail``, passing the
+username. Typically, this will be done as follows::
+
+    def get_absolute_url(self):
+        return ('profiles_profile_detail', (), { 'username': self.user.username })
+    get_absolute_url = models.permalink(get_absolute_url)
+
+In the default configuration, this application requires four
+templates to be created:
+
+``profiles/create_profile.html``
+    This will display the form for users to initially fill in their
+    profiles; it receives one variable -- ``form``, representing the
+    form for profile creation -- directly, and also uses
+    ``RequestContext`` so that context processors will be applied.
+
+``profiles/edit_profile.html``
+    This will display the form for users to edit their profiles; it
+    receives one variable -- ``form``, representing the form for
+    profile editing -- directly, and also uses ``RequestContext`` so
+    that context processors will be applied.
+
+``profiles/profile_detail.html``
+    This will publicly display a user's profile; it receives one
+    variable -- ``profile``, representing the profile object --
+    directly, and also uses ``RequestContext`` so that context
+    processors will be applied.
+
+``profiles/profile_list.html``
+    This will display a list of user profiles, using `the
+    list_detail.object_list generic view`_.
+
+For the default URL setup, add the following line to your root
+URLConf::
+
+   (r'^profiles/', include('profiles.urls')),
+
+This will set up the following URL patterns:
+
+* ``/profiles/create/`` will be the view for profile creation, and its
+  URL pattern is named ``profiles_create_profile``.
+
+* ``/profiles/edit/`` will be the view for profile editing, and its
+  URL pattern is named ``profiles_edit_profile``.
+
+* ``/profiles/<username>/`` will be the view for public display of a
+  user profile, and its URL pattern is named
+  ``profiles_profile_detail``.
+
+* ``/profiles/`` will be the view for a browsable list of user
+  profiles.
+
+For notes on more advanced usage, including customization of the form
+classes used for profile editing and creation and how to allow users
+to prevent public display of their profiles, see the file
+``views.txt`` in this directory.
+
+.. _the list_detail.object_list generic view: http://www.djangoproject.com/documentation/generic_views/#django-views-generic-list-detail-object-list
+
+==================
+User profile views
+==================
+
+
+This application provides four views related to the creation, editing
+and display of user profiles, which cover most aspects of the common
+case of using profiles. All of these views are configurable to varying
+extents via keyword arguments.
+
+
+``profiles.views.create_profile``
+=================================
+
+Create a profile for the user, if one doesn't already exist.
+
+If the user already has a profile, as determined by
+``request.user.get_profile()``, a redirect will be issued to the
+``profiles.views.edit_profile`` view. If no profile model has been
+specified in the ``AUTH_PROFILE_MODULE`` setting,
+``django.contrib.auth.models.SiteProfileNotAvailable`` will be raised.
+
+To specify the form class used for profile creation, pass it as the
+keyword argument ``form_class``; if this is not supplied, it will fall
+back to a ``ModelForm`` for the model specified in the
+``AUTH_PROFILE_MODULE`` setting.
+
+If you are supplying your own form class, it must define a method
+named ``save()`` which corresponds to the signature of ``save()`` on
+``ModelForm``, because this view will call it with ``commit=False``
+and then fill in the relationship to the user (which must be via a
+field on the profile model named ``user``, a requirement already
+imposed by ``User.get_profile()``) before finally saving the profile
+object. If many-to-many relations are involved, the convention
+established by ``ModelForm`` of looking for a ``save_m2m()`` method on
+the form is used, and so your form class should define this method.
+
+To specify a URL to redirect to after successful profile creation,
+pass it as the keyword argument ``success_url``; this will default to
+the URL of the ``profiles.views.profile_detail`` view for the new
+profile if unspecified.
+
+To specify the template to use, pass it as the keyword argument
+``template_name``; this will default to
+``profiles/create_profile.html`` if unspecified.
+
+Context:
+
+    form
+        The profile-creation form.
+
+Template:
+
+    ``template_name`` keyword argument, or
+    ``profiles/create_profile.html``.
+
+
+``profiles.views.edit_profile``
+===============================
+
+Edit a user's profile.
+
+If the user does not already have a profile (as determined by
+``User.get_profile()``), a redirect will be issued to the
+``profiles.views.create_profile`` view; if no profile model has been
+specified in the ``AUTH_PROFILE_MODULE`` setting,
+``django.contrib.auth.models.SiteProfileNotAvailable`` will be raised.
+
+To specify the form class used for profile editing, pass it as the
+keyword argument ``form_class``; this form class must have a
+``save()`` method which will save updates to the profile object. If
+not supplied, this will default to a ``ModelForm for the profile model.
+
+If you supply a form class, its ``__init__()`` method must accept an
+instance of the profile model as the keyword argument ``instance``.
+
+To specify the URL to redirect to following a successful edit, pass it
+as the keyword argument ``success_url``; this will default to the URL
+of the ``profiles.views.profile_detail`` view if not supplied.
+
+To specify the template to use, pass it as the keyword argument
+``template_name``; this will default to ``profiles/edit_profile.html``
+if not supplied.
+
+Context:
+
+    form
+        The form for editing the profile.
+    
+    profile
+        The user's current profile.
+    
+Template:
+    
+    ``template_name`` keyword argument or
+    ``profiles/edit_profile.html``.
+
+
+``profiles.views.profile_detail``
+=================================
+
+Detail view of a user's profile.
+
+If no profile model has been specified in the ``AUTH_PROFILE_MODULE``
+setting, ``django.contrib.auth.models.SiteProfileNotAvailable`` will
+be raised.
+
+If the user has not yet created a profile, ``Http404`` will be raised.
+
+If a field on the profile model determines whether the profile can be
+publicly viewed, pass the name of that field as the keyword argument
+``public_profile_field``; that attribute will be checked before
+displaying the profile, and if it does not return a ``True`` value,
+the ``profile`` variable in the template will be ``None``. As a
+result, this field must be a ``BooleanField``.
+
+To specify the template to use, pass it as the keyword argument
+``template_name``; this will default to
+``profiles/profile_detail.html`` if not supplied.
+
+Context:
+
+    profile
+        The user's profile, or ``None`` if the user's profile is not
+        publicly viewable (see the note about ``public_profile_field``
+        above).
+
+Template:
+
+    ``template_name`` keyword argument or
+    ``profiles/profile_detail.html``.
+
+
+``profiles.views.profile_list``
+===============================
+
+List of user profiles.
+
+If no profile model has been specified in the ``AUTH_PROFILE_MODULE``
+setting, ``django.contrib.auth.models.SiteProfileNotAvailable`` will
+be raised.
+
+If a field on the profile model determines whether the profile can be
+publicly viewed, pass the name of that field as the keyword argument
+``public_profile_field``; the ``QuerySet`` of profiles will be
+filtered to include only those on which that field is ``True`` (as a
+result, this field must be a ``BooleanField``).
+
+This view is a wrapper around the
+``django.views.generic.list_detail.object_list`` generic view, so any
+arguments which are legal for that view will be accepted, with the
+exception of ``queryset``, which will always be set to the default
+``QuerySet`` of the profile model, optionally filtered as described
+above.
+
+Template:
+
+    ``template_name`` keyword argument or
+    ``profiles/profile_list.html``.
+
+Context:
+
+    Same as the ``django.views.generic.list_detail.object_list``
+    generic view.

profiles/__init__.py

Empty file added.
+"""
+URLConf for Django user profile management.
+
+Recommended usage is to use a call to ``include()`` in your project's
+root URLConf to include this URLConf for any URL beginning with
+'/profiles/'.
+
+"""
+
+from django.conf.urls.defaults import *
+
+from profiles import views
+
+
+urlpatterns = patterns('',
+                       url(r'^create/$',
+                           views.create_profile,
+                           name='profiles_create_profile'),
+                       url(r'^edit/$',
+                           views.edit_profile,
+                           name='profiles_edit_profile'),
+                       url(r'^(?P<username>\w+)/$',
+                           views.profile_detail,
+                           name='profiles_profile_detail'),
+                       url(r'^$',
+                           views.profile_list,
+                           name='profiles_profile_list'),
+                       )

profiles/utils.py

+"""
+Utility functions for retrieving and generating forms for the
+site-specific user profile model specified in the
+``AUTH_PROFILE_MODULE`` setting.
+
+"""
+
+from django import forms
+from django.conf import settings
+from django.db.models import get_model
+
+from django.contrib.auth.models import SiteProfileNotAvailable
+
+
+def get_profile_model():
+    """
+    Return the model class for the currently-active user profile
+    model, as defined by the ``AUTH_PROFILE_MODULE`` setting. If that
+    setting is missing, raise
+    ``django.contrib.auth.models.SiteProfileNotAvailable``.
+    
+    """
+    if (not hasattr(settings, 'AUTH_PROFILE_MODULE')) or \
+           (not settings.AUTH_PROFILE_MODULE):
+        raise SiteProfileNotAvailable
+    profile_mod = get_model(*settings.AUTH_PROFILE_MODULE.split('.'))
+    if profile_mod is None:
+        raise SiteProfileNotAvailable
+    return profile_mod
+
+
+def get_profile_form():
+    """
+    Return a form class (a subclass of the default ``ModelForm``)
+    suitable for creating/editing instances of the site-specific user
+    profile model, as defined by the ``AUTH_PROFILE_MODULE``
+    setting. If that setting is missing, raise
+    ``django.contrib.auth.models.SiteProfileNotAvailable``.
+    
+    """
+    profile_mod = get_profile_model()
+    class _ProfileForm(forms.ModelForm):
+        class Meta:
+            model = profile_mod
+            exclude = ('user',) # User will be filled in by the view.
+    return _ProfileForm

profiles/views.py

+"""
+Views for creating, editing and viewing site-specific user profiles.
+
+"""
+
+from django.core.exceptions import ObjectDoesNotExist
+from django.core.urlresolvers import reverse
+from django.http import Http404
+from django.http import HttpResponseRedirect
+from django.shortcuts import get_object_or_404
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.views.generic.list_detail import object_list
+
+from django.contrib.auth.decorators import login_required
+from django.contrib.auth.models import User
+
+from profiles import utils
+
+
+def create_profile(request, form_class=None, success_url=None,
+                   template_name='profiles/create_profile.html'):
+    """
+    Create a profile for the current user, if one doesn't already
+    exist.
+    
+    If the user already has a profile, as determined by
+    ``request.user.get_profile()``, a redirect will be issued to the
+    :view:`profiles.views.edit_profile` view. If no profile model has
+    been specified in the ``AUTH_PROFILE_MODULE`` setting,
+    ``django.contrib.auth.models.SiteProfileNotAvailable`` will be
+    raised.
+    
+    To specify the form class used for profile creation, pass it as
+    the keyword argument ``form_class``; if this is not supplied, it
+    will fall back to a ``ModelForm`` for the model specified in the
+    ``AUTH_PROFILE_MODULE`` setting.
+    
+    If you are supplying your own form class, it must define a method
+    named ``save()`` which corresponds to the signature of ``save()``
+    on ``ModelForm``, because this view will call it with
+    ``commit=False`` and then fill in the relationship to the user
+    (which must be via a field on the profile model named ``user``, a
+    requirement already imposed by ``User.get_profile()``) before
+    finally saving the profile object. If many-to-many relations are
+    involved, the convention established by ``ModelForm`` of
+    looking for a ``save_m2m()`` method on the form is used, and so
+    your form class should define this method.
+    
+    To specify a URL to redirect to after successful profile creation,
+    pass it as the keyword argument ``success_url``; this will default
+    to the URL of the :view:`profiles.views.profile_detail` view for
+    the new profile if unspecified.
+    
+    To specify the template to use, pass it as the keyword argument
+    ``template_name``; this will default to
+    :template:`profiles/create_profile.html` if unspecified.
+    
+    Context:
+    
+    form
+        The profile-creation form.
+    
+    Template:
+    
+    ``template_name`` keyword argument, or
+    :template:`profiles/create_profile.html`.
+    
+    """
+    try:
+        profile_obj = request.user.get_profile()
+        return HttpResponseRedirect(reverse('profiles_edit_profile'))
+    except ObjectDoesNotExist:
+        pass
+    
+    #
+    # We set up success_url here, rather than as the default value for
+    # the argument. Trying to do it as the argument's default would
+    # mean evaluating the call to reverse() at the time this module is
+    # first imported, which introduces a circular dependency: to
+    # perform the reverse lookup we need access to profiles/urls.py,
+    # but profiles/urls.py in turn imports this module.
+    #
+    
+    if success_url is None:
+        success_url = reverse('profiles_profile_detail',
+                              kwargs={ 'username': request.user.username })
+    if form_class is None:
+        form_class = utils.get_profile_form()
+    if request.method == 'POST':
+        form = form_class(data=request.POST, files=request.FILES)
+        if form.is_valid():
+            profile_obj = form.save(commit=False)
+            profile_obj.user = request.user
+            profile_obj.save()
+            if hasattr(form, 'save_m2m'):
+                form.save_m2m()
+            return HttpResponseRedirect(success_url)
+    else:
+        form = form_class()
+    return render_to_response(template_name,
+                              { 'form': form },
+                              context_instance=RequestContext(request))
+create_profile = login_required(create_profile)
+
+def edit_profile(request, form_class=None, success_url=None,
+                 template_name='profiles/edit_profile.html'):
+    """
+    Edit the current user's profile.
+    
+    If the user does not already have a profile (as determined by
+    ``User.get_profile()``), a redirect will be issued to the
+    :view:`profiles.views.create_profile` view; if no profile model
+    has been specified in the ``AUTH_PROFILE_MODULE`` setting,
+    ``django.contrib.auth.models.SiteProfileNotAvailable`` will be
+    raised.
+    
+    To specify the form class used for profile editing, pass it as the
+    keyword argument ``form_class``; this form class must have a
+    ``save()`` method which will save updates to the profile
+    object. If not supplied, this will default to a ``ModelForm`` for
+    the profile model.
+    
+    If you supply a form class, its ``__init__()`` method must accept
+    an instance of the profile model as the keyword argument
+    ``instance``, and must implement a method named ``save()`` for
+    updating and saving the profile object from the form's data.
+    
+    To specify the URL to redirect to following a successful edit,
+    pass it as the keyword argument ``success_url``; this will default
+    to the URL of the :view:`profiles.views.profile_detail` view if
+    not supplied.
+    
+    To specify the template to use, pass it as the keyword argument
+    ``template_name``; this will default to
+    :template:`profiles/edit_profile.html` if not supplied.
+    
+    Context:
+    
+    form
+        The form for editing the profile.
+        
+    profile
+         The user's current profile.
+    
+    Template:
+    
+    ``template_name`` keyword argument or
+    :template:`profiles/edit_profile.html`.
+    
+    """
+    try:
+        profile_obj = request.user.get_profile()
+    except ObjectDoesNotExist:
+        return HttpResponseRedirect(reverse('profiles_create_profile'))
+    
+    #
+    # See the comment in create_profile() for discussion of why
+    # success_url is set up here, rather than as a default value for
+    # the argument.
+    #
+    
+    if success_url is None:
+        success_url = reverse('profiles_profile_detail',
+                              kwargs={ 'username': request.user.username })
+    if form_class is None:
+        form_class = utils.get_profile_form()
+    if request.method == 'POST':
+        form = form_class(data=request.POST, files=request.FILES, instance=profile_obj)
+        if form.is_valid():
+            form.save()
+            return HttpResponseRedirect(success_url)
+    else:
+        form = form_class(instance=profile_obj)
+    return render_to_response(template_name,
+                              { 'form': form,
+                                'profile': profile_obj, },
+                              context_instance=RequestContext(request))
+edit_profile = login_required(edit_profile)
+
+def profile_detail(request, username, public_profile_field=None,
+                   template_name='profiles/profile_detail.html'):
+    """
+    Detail view of a user's profile.
+    
+    If no profile model has been specified in the
+    ``AUTH_PROFILE_MODULE`` setting,
+    ``django.contrib.auth.models.SiteProfileNotAvailable`` will be
+    raised.
+    
+    If the user has not yet created a profile, ``Http404`` will be
+    raised.
+    
+    If a field on the profile model determines whether the profile can
+    be publicly viewed, pass the name of that field (as a string) as
+    the keyword argument ``public_profile_field``; that attribute will
+    be checked before displaying the profile, and if it does not
+    return a ``True`` value, the ``profile`` variable in the template
+    will be ``None``. As a result, this field must be a
+    ``BooleanField``.
+    
+    To specify the template to use, pass it as the keyword argument
+    ``template_name``; this will default to
+    :template:`profiles/profile_detail.html` if not supplied.
+    
+    Context:
+    
+    profile
+        The user's profile, or ``None`` if the user's profile is not
+        publicly viewable (see the note about ``public_profile_field``
+        above).
+    
+    Template:
+    
+    ``template_name`` keyword argument or
+    :template:`profiles/profile_detail.html`.
+    
+    """
+    user = get_object_or_404(User, username=username)
+    try:
+        profile_obj = user.get_profile()
+    except ObjectDoesNotExist:
+        raise Http404
+    if public_profile_field is not None and \
+       not getattr(profile_obj, public_profile_field):
+        profile_obj = None
+    return render_to_response(template_name,
+                              { 'profile': profile_obj },
+                              context_instance=RequestContext(request))
+
+def profile_list(request, public_profile_field=None,
+                 template_name='profiles/profile_list.html', **kwargs):
+    """
+    A list of user profiles.
+    
+    If no profile model has been specified in the
+    ``AUTH_PROFILE_MODULE`` setting,
+    ``django.contrib.auth.models.SiteProfileNotAvailable`` will be
+    raised.
+    
+    If a field on the profile model determines whether the profile can
+    be publicly viewed, pass the name of that field as the keyword
+    argument ``public_profile_field``; the ``QuerySet`` of profiles
+    will be filtered to include only those on which that field is
+    ``True`` (as a result, this field must be a ``BooleanField``).
+    
+    This view is a wrapper around the
+    :view:`django.views.generic.list_detail.object_list` generic view,
+    so any arguments which are legal for that view will be accepted,
+    with the exception of ``queryset``, which will always be set to
+    the default ``QuerySet`` of the profile model, optionally filtered
+    as described above.
+    
+    Context:
+    
+    Same as the :view:`django.views.generic.list_detail.object_list`
+    generic view.
+    
+    Template:
+    
+    ``template_name`` keyword argument or
+    :template:`profiles/profile_list.html`.
+    
+    """
+    profile_model = utils.get_profile_model()
+    queryset = profile_model._default_manager.all()
+    if public_profile_field is not None:
+        queryset = queryset.filter(**{ public_profile_field: True })
+    kwargs['queryset'] = queryset
+    return object_list(request, template_name=template_name, **kwargs)
+from distutils.core import setup
+
+setup(name='profiles',
+      version='0.1',
+      description='User-profile application for Django',
+      author='James Bennett',
+      author_email='james@b-list.org',
+      url='http://code.google.com/p/django-profiles/',
+      packages=['profiles'],
+      classifiers=['Development Status :: 4 - Beta',
+                   'Environment :: Web Environment',
+                   'Intended Audience :: Developers',
+                   'License :: OSI Approved :: BSD License',
+                   'Operating System :: OS Independent',
+                   'Programming Language :: Python',
+                   'Topic :: Utilities'],
+      )