I'm trying to save form data to an anonymous user, however I get the below error when trying to save some form data in a CreateView". I'm not clear what the issue is?
ValueError: Cannot assign "<SimpleLazyObject: <django.contrib.auth.models.AnonymousUser object at 0x11126bc18>>": "EUser.user" must be a "User" instance.
Models:
class EUser(models.Model):
online_account = models.BooleanField()
supplier1 = models.OneToOneField(SupplierAccount)
supplier2 = models.OneToOneField(SupplierAccount)
supplier3 = models.OneToOneField(SupplierAccount)
address = models.OneToOneField(Address)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
class SupplierAccount(models.Model):
supplier = models.ForeignKey(Supplier)
username = models.CharField(max_length=255)
password = models.CharField(max_length=255)
Form:
class ServiceTypeForm(forms.ModelForm):
# BOOL_CHOICES = ((False, 'No'), (True, 'Yes'))
# online_account = forms.BooleanField(widget=forms.RadioSelect(choices=BOOL_CHOICES))
def __init__(self, *args, **kwargs):
super(ServiceTypeForm, self).__init__(*args, **kwargs)
self.fields['service_type'].initial = 'D'
class Meta:
model = EUser
fields = ('service_type', 'online_account')
View:
class ServiceTypeView(CreateView):
form_class = ServiceTypeForm
template_name = "standard_form.html"
success_url = '/'
def form_valid(self, form):
form.instance.user = self.request.user
super().form_valid(form)
online_account = form.cleaned_data['online_account']
if online_account:
return redirect('../online')
else:
return redirect('../address')
If the user is not logged in, then request.user is an anonymous user. It doesn't make sense to assign an anonymous user to form.instance.user, because an anonymous user does not exist in the database or have a primary key.
How you change your code depends on how you want your application to work.
If you want to allow anonymous users to create service types, then
# if self.request.user.is_authenticated(): # Django < 1.10
if self.request.user.is_authenticated:
form.instance.user = self.request.user
For this to work, you would need to change the user field to make it optional.
user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True)
After making this change, you'll need to run makemigrations and then migrate, to update the database.
Another option would be to restrict the view to logged in users. In Django 1.9+, You can do this with the LoginRequiredMixin.
from django.contrib.auth.mixins import LoginRequiredMixin
class ServiceTypeView(LoginRequiredMixin, CreateView):
...
I think you can not use the AnonymousUser as value for a ForeignKey to a User.
You should keep is as Null in this case.
class EUser(models.Model):
...
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, default=None)
class ServiceTypeView(CreateView):
...
def form_valid(self, form):
if self.request.user.is_authenticated():
form.instance.user = self.request.user
...
Related
I've been trying to set up a basic private messaging system in Django using the generic CreateView.
I am currently having trouble with the "Receiver"/"To" field in my form. I tried to make it so it was a drop down field with the options being followers of the logged-in user.
Currently, the field is populating with the correct usernames (in this case, "testuser1") but it is throwing an error saying this field needs to be populated with an instance of the User object.
ValueError: Cannot assign "'testuser1'": "Message.reciever" must be a "User" instance.
Is there a way to have the form pass in the object of the username that is selected?
Model:
class Message(models.Model):
sender = models.ForeignKey(User, on_delete=models.CASCADE, related_name="sender")
reciever = models.ForeignKey(User, on_delete=models.CASCADE, related_name="reciever")
subject = models.CharField(max_length=128, default="-")
content = models.TextField()
send_date = models.DateTimeField(default=timezone.now)
User Relationships Model:
class UserRelationships(models.Model):
user_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name="following")
following_user_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name="followers")
created = models.DateTimeField(auto_now_add=True)
UPDATED Form:
class MessageCreateForm(forms.ModelForm):
class Meta:
model = Message
fields = ['sender', 'reciever', 'subject', 'content']
widgets = {'sender': forms.HiddenInput()}
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
follower_objects = kwargs.pop('follower_objects')
super(MessageCreateForm, self).__init__(*args, **kwargs)
self.fields['reciever'] = RecieverModelChoiceField(queryset=User.objects.filter(username__in=follower_objects))
View:
class MessageCreateView(LoginRequiredMixin, CreateView):
model = Message
template_name = 'message/compose.html'
form_class = MessageCreateForm
def get_initial(self):
initial = super().get_initial()
initial['sender'] = self.request.user
return initial
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
user = self.request.user
followers = user.followers.values_list('user_id', flat=True)
follower_objects = []
kwargs['user'] = self.request.user
kwargs['follower_objects'] = follower_objects
for id in followers:
follower = User.objects.get(id=id)
follower_objects.append(follower)
return kwargs
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
You have to use forms.ModelChoiceField instead of forms.ChoiceField
ForeignKey (model) > ModelChoiceField (form) - Default widget: Select
ModelChoiceField has attribute queryset.
You can filter field reciever.queryset directly in MessageCreateForm.__init__ method.
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(MessageCreateForm, self).__init__(*args, **kwargs)
self.fields['reciever'].queryset = user.followers
UPDATE:
You can set a custom ModelChoiceField that will return any label you want (more info).
from django.forms import ModelChoiceField
class RecieverModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return obj.username
or
def __init__(self, *args, **kwargs):
....
self.fields['reciever'].label_from_instance = lambda obj: "%s" % obj.username
I have a createview using CBV
class StudentCreate(LoginRequiredMixin, CreateView):
login_url = '/signin/'
model = Student
fields = ['first_name', 'last_name' ]
success_url = '/dashboard/'
Respective models.py
class Class_teacher(models.Model):
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
standard = models.IntegerField()
division = models.CharField(max_length=1)
subject = models.CharField(max_length=200)
email = models.CharField(max_length=30)
class Student(models.Model):
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
classteacher = models.ForeignKey('Class_teacher', on_delete=models.SET_NULL,blank=True, null=True )
The webapp has a login feature . When the user ( classteacher ) login they can add students. What I want is classteacher field in Student(model Form ) should be automatically set as user which is the classteacher. ( Classteacher ) and should be saved in the db after creating the student. Classteacher model is updated with respective required fields .
Look here for the various methods of a CreateView that you can override.
In your case, you want to override the form_valid() method, which is called when the new Student will be saved.
from django.shortcuts import get_object_or_404
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.classteacher = get_object_or_404(Class_teacher, email=self.request.user.email)
self.object.save()
return super().form_valid(form)
You need to define your own form_valid().
I assume the Teacher as a one to one Relationship with your User model.
def form_valid(self, form):
student = form.save(commit=False)
#retrieve the current logged_in teacher, of course you have to be sure this view is only accesible for teachers (in dispatch for exemple)
self.object.classteacher = self.request.user.teacher
self.object.save()
return super(StudentCreate, self).form_vaild(form)
#bonus the dispatch
def dispatch(self, request, *args, **kwargs):
#get the loged in user
if request.user.teacher:
return super(StudentCreate, self).dispatch(request, *args, **kwargs)
else:
raise Http404
I recently was able to figure out how to do this in the CreateView, but the same is not working for the UpdateView (Here's the original post on how to do it in the CreateView: (Django) Limited ForeignKey choices by Current User)
Essentially, I need it to display only the Universes created by the currently logged in user, but by default, it displays all universes.
When I try to set a form_class and have it mimic the solution for CreatView, it spits out an improperly configured error.
models.py:
class Universe(models.Model):
user = models.ForeignKey(User,related_name='universe',on_delete=models.CASCADE)
name = models.CharField(max_length=100, unique=True)
description = models.TextField(max_length=2000,blank=True,default="")
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('universe:singleuniverse',kwargs={'pk': self.pk})
class Meta:
ordering = ['name']
unique_together = ['user','name']
class Character(models.Model):
user = models.ForeignKey(User,related_name='characters',on_delete=models.CASCADE)
universe = models.ForeignKey("story_universe.Universe", on_delete=models.CASCADE)
name = models.CharField(max_length=255,unique=True)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('character_developer:singlecharacter',kwargs={'pk': self.pk})
class Meta():
ordering = ['name']
unique_together=['user','name']
views.py:
class UpdateCharacter(LoginRequiredMixin,generic.UpdateView):
model = Character
fields = ('universe','name')
template_name = 'character_developer/character_update_form.html'
UPDATE
The error was:
Error: ImproperlyConfigured at /characters/update/3/
UpdateCharacter is missing a QuerySet. Define UpdateCharacter.model, UpdateCharacter.queryset, or override UpdateCharacter.get_queryset().
and here's what the code looked like to get the error:
views.py:
class UpdateCharacter(LoginRequiredMixin,generic.UpdateView):
template_name = 'character_developer/character_update_form.html'
form_class = UpdateForm
def get_form_kwargs(self):
kwargs = super(UpdateCharacter,self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
forms.py
class UpdateForm(forms.ModelForm):
def __init__(self,*args,**kwargs):
user = kwargs.pop('user')
super(UpdateForm,self).__init__(*args,**kwargs)
self.fields['universe'].queryset = Universe.objects.filter(user=user)
class Meta:
model = Character
fields = ('universe','name')
I believe you need the following in your views.py (almost an exact extension of your CreateCharacter):
class UpdateCharacter(LoginRequiredMixin, generic.UpdateView):
model = Character
template_name ='character_developer/character_create.html'
form_class = UpdateForm
def get_form_kwargs(self):
kwargs = super(UpdateCharacter,self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self,form):
self.object = form.save(commit=False)
self.object.user = self.request.user
self.object.save
return super().form_valid(form)
I would caveat the above - make sure that the user instance in the request object is that of the currently logged in user, and has sufficient permission to update their own user - and can't somehow make a request on their behalf.
I have a simple Group model that users can be added.
class Group(models.Model):
name = models.CharField(max_length=50)
users = models.ManyToManyField(settings.AUTH_USER_MODEL)
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE,
related_name='admin_on_group')
date_created = models.DateTimeField(auto_now_add=True)
date_modifies = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
I have a basic CreateView for the group. The logged in user who creates the group get saved in the field created_by. I however ALSO want to save the same logged in user in the field users so that he can participate as a normal member of the group. The problem is that the view ends up ONLY saving the logged in user and the other users passed in from the form field users are not saved.
For example, If a user called 'george' creates a group, he should be added in the created_by and users as well. As of now, when I select other users in the form, only george gets saved in both fields.
class GroupCreateView(CreateView):
form_class = GroupForm
template_name = "groups/group_create.html"
def form_valid(self, form):
form = form.save(commit=False)
form.created_by = self.request.user
form.save()
# Apparently you can only add M2M relationships saves after first
# saving
form.users.add(User.objects.get(pk = self.request.user.pk))
return HttpResponseRedirect(reverse('group_list'))
def get_form_kwargs(self):
kwargs = super(GroupCreateView, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
I have a modelForm that has the following outline.
Note: The initial data passed in the self.fields['users'] below also doesn't show. I have also used a custom model that has phone_number as the USERNAME_FIELD. The querysets passed in the self.fields['users'] works.
class UserModelChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
return obj.get_full_name()
class GroupForm(forms.ModelForm):
class Meta:
model = Group
fields = ('name', 'users', )
def __init__(self, *args, **kwargs):
# popping the user from kwargs dictionary that has been
# passed in CreateView
user = kwargs.pop('user', None)
self.user = user # setting self.user to be equal to user above
super(GroupForm, self).__init__(*args, **kwargs)
self.fields['users'] = UserModelChoiceField(
queryset=User.objects.exclude(phone_number=str(user)),
initial=User.objects.get(phone_number=str(user))
)
Since you've saved the form with commit=False, you need to call the form's save_m2m() method to save the many-to-many data after you have saved the instance.
def form_valid(self, form):
instance = form.save(commit=False)
instance.created_by = self.request.user
instance.save()
form.save_m2m()
# Apparently you can only add M2M relationships saves after first
# saving
instance.users.add(self.request.user)
return HttpResponseRedirect(reverse('group_list'))
Note that I've changed the line to instance = form.save(commit=False) to make it clearer that save() returns an instance, and so that you still have access to the form.
I've implemented a form where I require fields in the User object to be populated (firstname, lastname, email) as well as fill out a new membership object. I've implemented this with a Function Based View (FBV) but I feel like I should be able to do this with a Class Based View (CBV). The heart of the problem seems to be referencing the current user in a form without passing in the user object. In FBV it's easy to do but I can't find any examples using CBV. I'm thinking that I must be missing something here.
Here is my code
models.py
class Membership(models.Model):
"""Represents an active membership of a user. Both the start_date and
end_date parameters are inclusive."""
DEFAULT_DURATION = 365 # the default number of days a membership is active
start_date = models.DateField(auto_created=True)
end_date = models.DateField(null=True)
membership_type = models.ForeignKey(MembershipType)
referral = models.ForeignKey(User, related_name='membership_referral', null=True)
# Contact Info
phone = PhoneNumberField()
# Address Fields
address_1 = models.CharField(max_length=255)
address_2 = models.CharField(max_length=255, blank=True)
city = models.CharField(max_length=64)
state = USStateField()
zip_code = USPostalCodeField()
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
"""Overload the save function to set the start and end date."""
self.start_date = datetime.date.today()
self.end_date = (self.start_date +
datetime.timedelta(days=self.membership_type.period))
super().save()
#property
def is_active(self):
return self.end_date >= datetime.date.today()
forms.py
class MembershipForm(ModelForm):
"""The Form shown to users when enrolling or renewing for membership."""
def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user", None)
_fields = ('first_name', 'last_name', 'email',)
_initial = model_to_dict(self.user, _fields) if self.user is not None else {}
super(MembershipForm, self).__init__(initial=_initial, *args, **kwargs)
self.fields.update(fields_for_model(User, _fields))
self.fields['referral'].required = False
class Meta:
model = Membership
fields = ['membership_type', 'referral', 'phone', 'address_1',
'address_2', 'city', 'state']
zip_code = USZipCodeField(max_length=5, required=True)
def save(self, *args, **kwargs):
self.user.first_name = self.cleaned_data['first_name']
self.user.last_name = self.cleaned_data['last_name']
self.user.email = self.cleaned_data['email']
self.user.save()
profile = super(MembershipForm, self).save(*args, **kwargs)
return profile
views.py
#login_required
def enroll(request):
template_name = 'enroll.html'
if request.method == 'POST':
form = MembershipForm(request.POST, user=request.user)
if form.is_valid():
form.save()
return redirect('/')
else:
form = MembershipForm(user=request.user)
return render(request, template_name, {'form': form})
You can access current user in class based view by self.request.user. It can be set in FormView by redefining validate method like this:
class YourView(CreateView)
...
def form_valid(self, form):
form.instance.user = self.request.user
return super(YourView, self).form_valid(form)
I have used CreateView instead of FormView in example because for edit you should check current instance's user in additional for security purposes.
Although your question mentions CBV, yet in the code you are using FBV.
In FBV you have access to request variable being passed. You can use request.user in this case.
In case of CBVs, Django allows you to access request object as self.request. In the implementation of default 'django.views.generic.base.View' class of CBV, they do this as first thing.
Check 4th line of def view as part of as_view in this code - https://github.com/django/django/blob/master/django/views/generic/base.py
All the objects, including user as part of request can be accessed as self.request.user.