Django form checkbox to change a value in UserProfile - django

I'm using Django-Profiles with Django 1.4, and I need a way to unsubscribe a user, so they can stop getting emails.
One of the fields in my UserProfile model is user_type, and I have a USER_TYPES list of choices. To keep users in the system, even if they unsubscribe, I decided to have one of the USER_TYPES be InactiveClient, and I'd include a checkbox like so:
Models.py:
USER_TYPES = (
('Editor', 'Editor'),
('Reporter', 'Reporter'),
('Client', 'Client'),
('InactiveClient', 'InactiveClient'),
('InactiveReporter', 'InactiveReporter'),
)
class UserProfile(models.Model):
user = models.OneToOneField(User, unique=True)
user_type = models.CharField(max_length=25, choices=USER_TYPES, default='Client')
... etc.
forms.py
class UnsubscribeForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(UnsubscribeForm, self).__init__(*args, **kwargs)
try:
self.initial['email'] = self.instance.user.email
self.initial['first_name'] = self.instance.user.first_name
self.initial['last_name'] = self.instance.user.last_name
except User.DoesNotExist:
pass
email = forms.EmailField(label='Primary Email')
first_name = forms.CharField(label='Editor first name')
last_name = forms.CharField(label='Editor last name')
unsubscribe = forms.BooleanField(label='Unsubscribe from NNS Emails')
class Meta:
model = UserProfile
fields = ['first_name','last_name','email','unsubscribe']
def save(self, *args, **kwargs):
u = self.instance.user
u.email = self.cleaned_data['email']
u.first_name = self.cleaned_data['first_name']
u.last_name = self.cleaned_data['last_name']
if self.unsubscribe:
u.get_profile().user_type = 'InactiveClient'
u.save()
client = super(UnsubscribeForm, self).save(*args,**kwargs)
return client
Edit: I've added additional code context. if self.unsubscribe: is in save() override. Should that be somewhere else? Thank you.
Edit2: I've tried changing UnsubscribeForm in several ways. Now I get a 404, No User matches the given query. But the view function being called works for other forms, so I'm not sure why?
urls.py
urlpatterns = patterns('',
url('^client/edit', 'profiles.views.edit_profile',
{
'form_class': ClientForm,
'success_url': '/profiles/client/edit/',
},
name='edit_client_profile'),
url('^unsubscribe', 'profiles.views.edit_profile',
{
'form_class': UnsubscribeForm,
'success_url': '/profiles/client/edit/',
},
name='unsubscribe'),
)
These two urls are calling the same view, just using a different form_class.
Edit3: So I don't know why, but when I removed the trailing slash from the unsubscribe url, the form finally loads. But when I submit the form, I still get an error: 'UnsubscribeForm' object has no attribute 'unsubscribe' If anyone could help me understand why a trailing slash would cause the 404 error (No User matches the given query) I wouldn't mind knowing. But as of now, the form loads, but doesn't submit, and the trace ends on this line of my form:
if self.unsubscribe:

Answering my own question again. On ModelForms, you can add form elements that don't exist in the model, and access the value of those fields by accessing self.cleaned_data['form_element_name'] in the save method.
This is what my save method looks like:
def save(self, *args, **kwargs):
u = self.instance.user
p = self.instance.user.get_profile()
u.email = self.cleaned_data['email']
u.first_name = self.cleaned_data['first_name']
u.last_name = self.cleaned_data['last_name']
if self.cleaned_data['unsubscribe']:
p.user_type = 'InactiveClient'
u.save()
p.save()
client = super(UnsubscribeForm, self).save(*args,**kwargs)
return client

Related

Cannot assign "": "" must be a '' instance

I am trying to make a registration key in the UserModel where the key field in the registration form is a foreign key to another model called RegistrationKey. I have made two posts about this topic earlier without any success, however has a few things changed in my code which makes those previous posts irrelevant. In the form field, the field for the key is a CharField as I can not display the keys for the users due to safety.
These are the two previous posts:
Save user input which is a string as object in db ,
Textinput with ModelChoiceField
These are my two models.
class RegistrationKey(models.Model):
key = models.CharField(max_length=30)
def __str__(self):
return self.key
class User(AbstractUser):
key = models.ForeignKey(RegistrationKey, on_delete=models.CASCADE, null=True, blank=True)
Since my latest posts have I created a class based view, which looks like this:
class RegisterPage(CreateView):
form_class = MyUserCreationForm
def form_valid(self, form):
key = form.cleaned_data['key']
try:
keyobject = RegistrationKey.objects.get(key=key)
form.instance.key = keyobject
return super().form_valid(form)
except RegistrationKey.DoesNotExist:
form.add_error('key', 'error')
return super().form_invalid(form)
When I try and pass in the value Admin which is an object in the RegistrationKey model I get the following error:
'Cannot assign "'Admin'": "User.key" must be a "RegistrationKey" instance.'
I don't know how to solve this, how can this string that the user inputs be assigned to the db?
Edit
Here are my form
class MyUserCreationForm(UserCreationForm):
key = forms.CharField(widget=forms.TextInput(attrs={'class':'form-control', 'placeholder':'Key'}), label='')
email = forms.EmailField(widget=forms.EmailInput(attrs={'class':'form-control', 'placeholder':'Email'}), label='')
class Meta:
model = User
fields = ('key', 'email', 'password1', 'password2')
def __init__(self, *args, **kwargs):
super(MyUserCreationForm, self).__init__(*args, **kwargs)
self.fields['password1'].widget.attrs['class'] = 'form-control'
self.fields['password1'].widget.attrs['placeholder'] = 'Password'
self.fields['password1'].label=''
self.fields['password2'].widget.attrs['class'] = 'form-control'
self.fields['password2'].widget.attrs['placeholder'] = 'Confirm Password'
self.fields['password2'].label=''
for fieldname in ['password1', 'password2']:
self.fields[fieldname].help_text = None
You better move the logic to obtain the item to the form, where it belongs. So with:
from django.core.exceptions import ValidationError
class MyUserCreationForm(UserCreationForm):
# …
def clean_key(self):
key = self.cleaned_data['key']
try:
return RegistrationKey.objects.get(key=key)
except RegistrationKey.DoesNotExist:
raise ValidationError('The key is not valid.')
That should be sufficient. You should not override the .form_valid(…) method.

Django: Foreign Key to User -> Form is not validating because field is required

I'm currently creating a Registration-Page with two parts
One part is about the Username and a Passwort.
The second part is about choosing the own PC-Configuration
After defining everything, the User can register to get to the Main-Page.
Therefore I got a Model called "PC_Configuration" with a bunch of Foreign-Keys to the different Database-Models of the Processors/Graphicscards etc.:
class PC_Configuration(models.Model):
user = models.ForeignKey(User, related_name='user_id', on_delete=models.DO_NOTHING)
processor = models.ForeignKey(Processors, related_name='processor_id', on_delete=models.DO_NOTHING)
graphicscard = models.ForeignKey(Graphicscard, related_name='graphicscard_id', on_delete=models.DO_NOTHING)
os = models.ForeignKey(OS, related_name='os_id', on_delete=models.DO_NOTHING)
ram = models.ForeignKey(RAM, related_name='ram_id', on_delete=models.DO_NOTHING)
harddrive = models.ForeignKey(Harddrive, related_name='harddrive_id', on_delete=models.DO_NOTHING)
Also, there is one ForeignKey to the User to connect the Configuration to the respective User-ID.
Inside views.py, I've been creating a DropdownForm for all the Dropdown-Fields which the User shall choose on his own:
class DropdownForm(forms.ModelForm):
class Meta:
model = models.PC_Configuration
exclude = []
def __init__(self, *args, **kwargs):
super(DropdownForm, self).__init__(*args, **kwargs)
self.fields['processors'].queryset = DropdownForm.objects.all()
self.fields['processors'].label_from_instance = lambda obj: "%s" % obj.name
self.fields['graphicscard'].queryset = DropdownForm.objects.all()
self.fields['graphicscard'].label_from_instance = lambda obj: "%s" % obj.name
self.fields['os'].queryset = DropdownForm.objects.all()
self.fields['os'].label_from_instance = lambda obj: "%s" % obj.name
self.fields['ram'].queryset = DropdownForm.objects.all()
self.fields['ram'].label_from_instance = lambda obj: "%s" % obj.name
self.fields['harddrive'].queryset = DropdownForm.objects.all()
self.fields['harddrive'].label_from_instance = lambda obj: "%s" % obj.name
But regarding the fact, that the User-ID shall be assigned to the Configuration automatically, there's no field for that in here.
It is defined in the register_view(request) - Method:
def register_view(request):
form = DropdownForm()
if request.method == "POST":
form = DropdownForm(request.POST)
username = request.POST.get('username')
password = request.POST.get('password')
myuser = User.objects.create_user(username, None, password)
myuser.save()
auth.login(request, myuser)
#form.user = request.user
print(form.errors)
if form.is_valid():
instance = form.save(commit=False)
instance.user = request.user
instance.save()
messages.success(request, "Account has been created successfully")
return redirect(reverse('gamesearch_view'))
else:
print('Failed')
form = DropdownForm()
render(request, 'register.html', dict(form=form))
return render(request, 'register.html', dict(form=form))
And in here, we got the problem, I guess.
While Testing the Registration, the Testaccounts keep creating and login successfully. But the problem is, that there's no PC-Configuration created because the form is not validating.
With
print(form.errors)
I've been trying to figure out why exactly and it said
<ul class="errorlist"><li>user<ul class="errorlist"><li>This field is required.</li></ul></li></ul>
So it seems like it's necessary to define the "user"-field before checking, if the form is validating and defining the user inside an instance afterwards.
That's why I was trying to do this:
form.user = request.user
But it's still not working and I can't figure out, what's exactly the problem since "user" shouldn't be part of the form-validation.
Can you help me out here?
Thank you in Advance!
You'll have a simpler time with something like this...
Your related_names were somewhat bogus; they're supposed to be the reverse name from the "viewpoint" of the other model. (Also, you never need to add _id to your fields by hand in Django.) If you elide the related_names, they'll implicitly be pc_configuration_set.
on_delete=DO_NOTHING is likely not a good idea. PROTECT is a good default.
It's easier to just handle the username and password as fields in the form.
You were missing exclude = ["user"], so if your template didn't render a field for user, of course it'd be missing. However, you also don't want the POSTer of the form to submit any old user id.
Using a FormView removes most of the boilerplate required to manage forms.
We're using transaction.atomic() to make sure the user doesn't get finally saved to the database if saving the PC Configuration fails.
We assign the created user to form.instance, which is the new but as-of-yet unsaved PC Configuration.
(Of course, imagine these are in separate files.)
from django import forms
from django.db import models, transaction
from django.views.generic import FormView
class PC_Configuration(models.Model):
user = models.ForeignKey(User, on_delete=models.PROTECT)
processor = models.ForeignKey(Processors, on_delete=models.PROTECT)
graphicscard = models.ForeignKey(Graphicscard, on_delete=models.PROTECT)
os = models.ForeignKey(OS, on_delete=models.PROTECT)
ram = models.ForeignKey(RAM, on_delete=models.PROTECT)
harddrive = models.ForeignKey(Harddrive, on_delete=models.PROTECT)
class RegisterAndConfigurePCForm(forms.ModelForm):
username = forms.CharField(required=True)
password = forms.CharField(required=True, widget=forms.PasswordInput())
class Meta:
model = PC_Configuration
exclude = ["user"] # we'll assign this by hand
class RegisterAndConfigureView(FormView):
form_class = RegisterAndConfigurePCForm
template_name = "register.html"
def form_valid(self, form):
with transaction.atomic():
user = User.objects.create_user(form.cleaned_data["username"], None, form.cleaned_data["password"])
form.instance.user = user # assign user to the to-be-created PC configuration
form.save()
return redirect(reverse("gamesearch_view"))

Make ForeignKey optional in Django Admin?

I have a custom django admin page, and I would like to make the two ForeignKey fields optional in the admin interface. I do not want to change the underlying model.
This is the model:
class IncorporationTicket(models.Model, AdminURL):
ordered_by = models.ForeignKey('Organisation', # organisation which ordered this
null = True,
blank = False, # i.e. can only be null as a result of delete
on_delete = models.SET_NULL)
ordered_by_individual = models.ForeignKey('Individual', # individual at organisation which ordered this
null = True,
blank = False, # i.e. can only be null as a result of delete
on_delete = models.SET_NULL)
(AdminURL is a mixin which provides get_absolute_url)
This is the ModelAdmin:
class TicketAdmin(admin.ModelAdmin):
readonly_fields = ('ordered', 'charge', 'amount_paid', 'submitted_on')
formfield_overrides = {
models.ForeignKey: {'required': False},
}
def formfield_for_foreignkey(self, db_field, request, **kwargs):
pk = resolve(request.path).args[0] # the request url should only have one arg, the pk
instance = self.get_object(request, pk)
user = request.user
kwargs['required'] = False # will be passed to every field
if db_field.name == "ordered_by_individual":
# queryset should be a union of (a) individual already set on object (b) individual for current user
## None option is provided by admin interface - just need to let field be optional.
if instance.ordered_by_individual:
kwargs["queryset"] = (
Individual.objects.filter(pk = instance.ordered_by_individual.pk) |
user.individual_set.all())
else: kwargs["queryset"] = user.individual_set.all()
elif db_field.name == "ordered_by":
# queryset should be a union of (a) organisation already set (b) any organisations for which user is authorised
try:
individual = user.individual_set.all()[0]
all_orgs = Organisation.all_organisations_for_which_individual_authorised_to_incorporate(individual)
except:
all_orgs = Organisation.objects.none()
if instance.ordered_by:
kwargs["queryset"] = (
Organisation.objects.filter(pk = instance.ordered_by.pk) |
all_orgs)
else: kwargs["queryset"] = all_orgs
return super(type(self), self).formfield_for_foreignkey(db_field, request, **kwargs)
As you can see, I have tried to use both formfield_overrides, and formfield_for_foreignkey to set required = False on the FormField, but it is not having the required effect: when attempting to save through the admin interface without setting (that is, leaving the field in its original blank state), the admin interface shows the error 'This field is required.'
So, my question is: how does one prevent the underlying form from requiring certain fields, while still also setting the choices in formfield_for_foreignkey?
While I'm not sure why kwargs['required'] wouldn't work, you could always override the admin form with your own form. It hasn't failed me with magical django admin behavior so it's a pretty good bet.
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.fields['my_fk_field'].required = False
class Meta:
model = MyModel
class MyAdmin(admin.ModelAdmin):
form = MyForm
This would still allow you to modify the QuerySet via the formfield_for_foo methods.
... almost 9 years later, in Django v3.1.2 ...
blank=True works fine for me:
from django.contrib.auth.models import User
owner = models.ForeignKey(User,
related_name="notes",
on_delete=models.CASCADE,
null=True,
blank=True)
(the solution has been taken from here)

How to make optionally read-only fields in django forms?

I have a read-only field in a django form that I sometimes want to edit.
I only want the right user with the right permissions to edit the field. In most cases the field is locked, but an admin could edit this.
Using the init function, I am able to make the field read-only or not, but not optionally read-only. I also tried passing an optional argument to StudentForm.init but that turned much more difficult that I expected.
Is there a proper way to do accomplish this?
models.py
class Student():
# is already assigned, but needs to be unique
# only privelidged user should change.
student_id = models.CharField(max_length=20, primary_key=True)
last_name = models.CharField(max_length=30)
first_name = models.CharField(max_length=30)
# ... other fields ...
forms.py
class StudentForm(forms.ModelForm):
class Meta:
model = Student
fields = ('student_id', 'last_name', 'first_name',
# ... other fields ...
def __init__(self, *args, **kwargs):
super(StudentForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance:
self.fields['student_id'].widget.attrs['readonly'] = True
views.py
def new_student_view(request):
form = StudentForm()
# Test for user privelige, and disable
form.fields['student_id'].widget.attrs['readonly'] = False
c = {'form':form}
return render_to_response('app/edit_student.html', c, context_instance=RequestContext(request))
Is that what you are looking for? By modifying your code a little bit:
forms.py
class StudentForm(forms.ModelForm):
READONLY_FIELDS = ('student_id', 'last_name')
class Meta:
model = Student
fields = ('student_id', 'last_name', 'first_name')
def __init__(self, readonly_form=False, *args, **kwargs):
super(StudentForm, self).__init__(*args, **kwargs)
if readonly_form:
for field in self.READONLY_FIELDS:
self.fields[field].widget.attrs['readonly'] = True
views.py
def new_student_view(request):
if request.user.is_staff:
form = StudentForm()
else:
form = StudentForm(readonly_form=True)
extra_context = {'form': form}
return render_to_response('forms_cases/edit_student.html', extra_context, context_instance=RequestContext(request))
So the thing is to check permissions on the views level, and then to pass argument to your form when it is initialized. Now if staff/admin is logged in, fields will be writeable. If not, only fields from class constant will be changed to read only.
It would be pretty easy to use the admin for any field editing and just render the student id in the page template.
I'm not sure if this answers your questions though.

Django Initial for a ManyToMany Field

I have a form that edits an instance of my model. I would like to use the form to pass all the values as hidden with an inital values of username defaulting to the logged in user so that it becomes a subscribe form. The problem is that the normal initial={'field':value} doesn't seem to work for manytomany fields. how do i go about it?
my views.py
#login_required
def event_view(request,eventID):
user = UserProfile.objects.get(pk=request.session['_auth_user_id'])
event = events.objects.get(eventID = eventID)
if request.method == 'POST':
form = eventsSusbcribeForm( request.POST,instance=event)
if form.is_valid():
form.save()
return HttpResponseRedirect('/events/')
else:
form = eventsSusbcribeForm(instance=event)
return render_to_response('event_view.html', {'user':user,'event':event, 'form':form},context_instance = RequestContext( request ))
my forms.py
class eventsSusbcribeForm(forms.ModelForm):
eventposter = forms.ModelChoiceField(queryset=UserProfile.objects.all(), widget=forms.HiddenInput())
details = forms.CharField(widget=forms.Textarea(attrs={'cols':'50', 'rows':'5'}),label='Enter Event Description here')
date = forms.DateField(widget=SelectDateWidget())
class Meta:
model = events
exclude = ('deleted')
def __init__(self, *args, **kwargs):
super(eventsSusbcribeForm, self).__init__(*args, **kwargs)
self.fields['username'].initial = (user.id for user in UserProfile.objects.filter())
my models.py
class events(models.Model):
eventName = models.CharField(max_length=100)
eventID = models.AutoField(primary_key=True)
details = models.TextField()
attendanceFee = models.FloatField(max_length=99)
date = models.DateField()
username = models.ManyToManyField(UserProfile, related_name='user', blank=True)
eventposter = models.ForeignKey(UserProfile, related_name='event_poster')
deleted = models.BooleanField()
def __unicode__(self):
return u'%s' % (self.eventName)
Can you post your Event model? It's too hard to guess what you are trying to do without that. I have to assume a few things without it, so I'm sorry if I'm wrong.
First off, I'm guessing that you should not be using an Event ModelForm for the EventSubscriptionForm. That doesn't really make sense. Hopefully, you created a through class for Event and User, so in your Event model, you have something like
subscriber_users = models.ManyToManyField(User, through="Subscription")
and
class Subscription(models.Model):
user = models.ForeignKey(User, related_name="events",)
event = models.ForeignKey(Event, related_name="subscribers")
Then you can use a Subscription ModelForm.
Is there any reason you're using eventID instead of the django idiom, event_id? You should also import your Event and EventSubcribeForm classes with Pythonic casing. One very important thing is that you should be linking everything to User and not UserProfile.
Technically, it makes more sense to set initial in the view rather than the form init, because you would have to pass request.user to init anyway.
I think you should try this for your view...
#login_required
def event_view(request, event_id=None):
user = request.user.get_profile()
event = Event.objects.get(id=event_id)
initial = {'user': request.user}
form = EventSubcriptionForm(request.POST or None, instance=event, initial=initial)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('event_list'))
return render_to_response('event_view.html', {
'event': event,
'form': form
}, context_instance = RequestContext(request))
A few notes
use request.user.get_profile() for the current user's profile object
you can use request.POST or None to avoid the request.method cases
always use named urls so you can reverse on names instead of hard-coding urls into views
if you want user in your template context, just setup a context processor (see pinax for example on how to do this) instead of passing it in every single view. You can always use request.user also.
Keep in mind that this code will only work if you have that through class setup like I said and you use a form like
class EventSubcriptionForm(forms.ModelForm):
class Meta:
model = Subscription
exclude = ('event')
EDIT
Thanks a bunch for the ups. I'm not new to django, but somehow very new to SO.
Okay, you should really read some of the PEPs about Python conventions http://www.python.org/dev/peps/pep-0008/ or some SO posts about it What is the naming convention in Python for variable and function names?.
Here's what I recommend for your event app models.py:
class Event(models.Model):
name = models.CharField(max_length=100)
details = models.TextField()
attendance_fee = models.FloatField(max_length=99)
date = models.DateField()
poster = models.ForeignKey(User, related_name='events_posted')
deleted = models.BooleanField()
attendee_users = models.ManyToManyField(User, through="Attendance")
def __unicode__(self):
return self.name
class Attendance(models.Model):
user = models.ForeignKey(User, related_name="events",)
event = models.ForeignKey(Event, related_name="attendees")
Notes
The name of a class is capitalized and singular. You are not describing events, you are the blueprint for an Event.
you never need the name of the class in its attributes, i.e. event_name can just be name.
all variables are lowercase_and_underscored
always link to User, not your profile model. A lot of django code expects this.
So now you can access the users attending the event with event.attendees.
I found this while trying to set defaults for the manytomany. I didn't want to add a through table.
based on the view Casey posted, but adding the user in the manytomany relation.
for the initial post:
#login_required
def event_view(request, event_id=None):
user = request.user.get_profile()
event = Event.objects.get(id=event_id)
initial = {'user': request.user, 'username': [ request.user.id, ] } # makes the poster also an attendee
form = EventSubcriptionForm(request.POST or None, instance=event, initial=initial)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('event_list'))
return render_to_response('event_view.html', {
'event': event,
'form': form
}, context_instance = RequestContext(request))
updated version:
#login_required
def event_view(request, event_id=None):
user = request.user.get_profile()
event = Event.objects.get(id=event_id)
initial = {'user': request.user, 'subscriber_users': [ request.user.id, ] } # makes the poster also an subscriber
form = EventSubcriptionForm(request.POST or None, instance=event, initial=initial)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('event_list'))
return render_to_response('event_view.html', {
'event': event,
'form': form
}, context_instance = RequestContext(request))