views:

176

answers:

4

The site I'm working on involves teachers creating student objects. The teacher can choose to make it possible for a student to log into the site (to check calendars, etc) OR the teacher can choose to use the student object only for record keeping and not allow the student to log in. In the student creation form, if the teacher supplies a username and a password, it should create an object of the first kind - one that can log in, i.e. a regular User object. If the teacher does not supply a username/password, it should create the second type. The other requirement is that the teacher should be able to go in later and change a non-logging-in student to the other kind. What's the best way to design for this scenario? Subclass User and make username and password not required? What else would this affect?

Edit: I ended up using User.set_unusable_password(). Here's the code - I've left out other forms, etc, that I'm also using in my view:

Form

class StudentForm(forms.ModelForm):
    username = forms.RegexField(regex=r'^\w+$',
                                required=False,
                                max_length=30,
                                label=("Username"),
                                error_messages={ 'invalid': ("This value must contain only letters, numbers and underscores.") })
    password = forms.CharField(widget=forms.PasswordInput(),
                                    label="Password", required=False)

    class Meta:
        model = User
        fields = ('first_name', 'last_name', 'username', 'email', 'password')

Note that username and password are not required in the form.

View

def create_student(request):
    if request.method == "POST":
        student_form = StudentForm(request.POST)
        if student_form.is_valid():
            user = student_form.save(commit=False)
            if student_form.cleaned_data['username'] == '':
                user.username = generate_random_username()
                user.set_unusable_password()
            else:
                user.set_password(user.password)
            user.save()

            return HttpResponseRedirect(reverse('student_list', args=['active']))

    #GET an empty form
    else:
        student_form = StudentForm()

return render_to_response('priviostudio/create_student.html', {
    'student_form': student_form,
})

And in the view to edit a student (which will probably be combined with the create_student view) I have this for GET:

student_form_initial = {
        'username': user_instance.username if user_instance.has_usable_password() else '',
        'password': user_instance.password if user_instance.has_usable_password() else '',
    }
    student_form = StudentForm(instance=user_instance, initial=student_form_initial)

And in POST, if the teacher submits a new username and valid password, I'll just set those on the User instance.

Thanks for the ideas everyone.

+1  A: 

You might consider making all the students one kind of object - not User - and then supplementing that model with user objects where the teacher has enabled the student to log in. The composition of these two model objects would solve you want fairly cleanly.

heckj
This seems like a good way of doing it, but the form might get a little messy it seems. I'll go try this.
jobrahms
I'm running into trouble with this: I would still like to use first_name, last_name, and email on User. Doing it this way would require keeping that data in the non-User object also and synching them up.
jobrahms
+3  A: 

The auth app's User model has a set_unusable_password method; this probably does what you want without requiring the model to be extended.

Ben James
The problem is that I would also have to generate a random username (since username has unique=True and does not have blank=True), which would also show up on forms and would be potentially confusing to the teachers.
jobrahms
A: 

I woud avoid subclassing User. Instead, you might want to create a custom authenticator that allows you to check group membership for login capability.

"""
DummyBackend.py

"""

from django.contrib.auth.models import User, check_password
from django.contrib.auth.backends import RemoteUserBackend
from lecture_feedback.daily.models import Student
class DummyBackend(RemoteUserBackend):
    """
    Dummy authentication module that takes a username and password. The password must match the username.
    """

    def authenticate(self, username=None, password=None):
        """
        The username passed as ``remote_user`` is considered trusted.  This
        method simply returns the ``User`` object with the given username,
        creating a new ``User`` object if ``create_unknown_user`` is ``True``.

        Returns None if ``create_unknown_user`` is ``False`` and a ``User``
        object with the given username is not found in the database.

        """

        try:
            student = Student.objects.get(globalid=username)
        except Student.DoesNotExist:
            return None

        if username != password:
            return
        user = None

        # Note that this could be accomplished in one try-except clause, but
        # instead we use get_or_create when creating unknown users since it has
        # built-in safeguards for multiple threads.
        if self.create_unknown_user:
            user, created = User.objects.get_or_create(username=username)
            if created:
                user = self.configure_user(user)
        else:
            try:
                user = User.objects.get(username=username)
            except User.DoesNotExist:
                pass
        return user


    def configure_user(self, user):
         """
         Configures a user after creation and returns the updated user.

         By default, returns the user unmodified.
         """
         student = Student.objects.get(globalid=user.username)
         user.first_name = student.first_name
         user.last_name = student.last_name
         return user

The Student model could contain a field that indicates if the student is allowed to log in. Also take a look at http://docs.djangoproject.com/en/dev/howto/auth-remote-user/#attributes.

John Percival Hackworth
+2  A: 

The Django default User model has an is_active field.

http://docs.djangoproject.com/en/dev/topics/auth/#django.contrib.auth.models.User.is_active

you probably want to use that.

That way when a teacher decides they want the user to be able to log in, your code would just set the student's user to is_active=True and vice versa.

Also according to the documentation link above Django's default authentication form and permission methods check the is_active flag, so that's some of the work already done for you.

You'd probably need to generate a username for the student as well, but you can easily do that using a slug from the name if that is provided.

The most obvious way for me to differentiate students from teachers would be groups really and Django provides mechanisms for that as well.

Hassan