Django design question: extending User to make users that can't log in
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.
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.
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.
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.
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.
精彩评论