class Event(models.Model):
...
class Question(models.Model):
event = models.ForeignKey(Event)
And I have url pattern like /events/(?P<event_id>\d+)/question/add/$ bound to QuestionCreateView
QuestionCreateView(CreateView):
model = Question
form_class = QuestionForm
def form_valid(self, form):
form.instance.event = [???]
return super(QuestionCreateView, self).form_valid(form)
What I'd like to get:
throw a 404 error if user requests invalid event_id like /events/9999999/objects/add/
get an Event instance from url's event_id and populate my new Question instance before saving
do it in a DRY way, since I have some other models with relations like this
Is it possible with class-based views? It looks like some crazy mix of DetailView for Event and CreateView for Question.
Url keyword arguments are available in the view as self.kwargs:
from django.shortcuts import get_object_or_404
class QuestionCreateView(CreateView):
model = Question
form_class = QuestionForm
def form_valid(self, form):
form.instance.event = get_object_or_404(Event,
pk=self.kwargs['event_id'])
return super(QuestionCreateView, self).form_valid(form)
Related
I have a CreateView to which after creation I'd like the user to be directed to the DetailView for that newly created instance.
class ModelCreateView(LoginRequiredMixin, CreateView):
model = Model
template_name = 'models/model-create-template.html'
In my get_success_url method, I use self.object.pk, but it is None.
def get_success_url(self):
return f'/model/{self.object.pk}'
class ModelCreateView(LoginRequiredMixin, CreateView):
model = Model
template_name = 'models/model-create-template.html'
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
def get_success_url(self):
return f'/model/{self.object.pk}'
Model for reproduction:
class Model(models.Model):
id = models.IntegerField(primary_key=True)
Using this model with the code above will reproduce that self.object.pk or self.object.id is None within get_success_url.
I can see that I reach a successful save() since my post_save signals are firing, and form_valid() is called as well, since I update the user in this method.
When I look in my debugger at self.object, all of the other fields are populated, by id and pk are both None. Looking at the instance in admin, I can confirm these are actually being set correctly, but for some reason at the time of calling get_success_url I don't have access to this data.
Is the instance in self.object actually the created instance, or is it just representing the fields submitted by the CreateView form? If its the former, how could I access the instance in get_success_url?
Consider the FormView that I'm overriding, below. Upon successful creation of a new League record, how do I reference that record so that I can redirect the user to a more general edit page specific to that League?
class LeagueView(FormView):
template_name = 'leagueapp/addleague.html'
form_class = LeagueForm
def form_valid():
newleague = ??? #newly created league
success_url = '/league/editleague/' + str(newleague.id)
return HttpResponseRedirect(self.get_success_url())
this is really straight forward, given an url in the league namespace like
url(r'^league/editleague/(?P<id>\d+>)$', LeagueView.as_view(), name='edit')
you should edit the form_valid method in this way:
from django.shortcuts import redirect
class LeagueView(FormView):
template_name = 'leagueapp/addleague.html'
form_class = LeagueForm
def form_valid(self, form):
newleague = form.save()
return redirect('league:edit', id=newleague.id)
I'm making an election information app, and I want to allow the currently logged-in user to be able to declare himself and only himself as a candidate in an election.
I'm using Django's built-in ModelForm and CreateView. My problem is that the Run for Office form (in other words, the 'create candidate' form) allows the user to select any user in the database to make a candidate.
I want the user field in the Run for Office to be automatically set to the currently logged-in user, and for this value to be hidden, so the logged-in user cannot change the value of the field to someone else.
views.py
class CandidateCreateView(CreateView):
model = Candidate
form_class = CandidateForm
template_name = 'candidate_create.html'
def form_valid(self, form):
f = form.save(commit=False)
f.save()
return super(CandidateCreateView, self).form_valid(form)
forms.py
class CandidateForm(forms.ModelForm):
class Meta:
model = Candidate
models.py
class Candidate(models.Model):
user = models.ForeignKey(UserProfile)
office = models.ForeignKey(Office)
election = models.ForeignKey(Election)
description = models.TextField()
def __unicode__(self):
return unicode(self.user)
def get_absolute_url(self):
return reverse('candidate_detail', kwargs={'pk': str(self.id)})
Remove user field from rendered form (using exclude or fields, https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#selecting-the-fields-to-use )
class CandidateForm(forms.ModelForm):
class Meta:
model = Candidate
exclude = ["user"]
Find user profile and set user field in the create view.
class CandidateCreateView(CreateView):
...
def form_valid(self, form):
candidate = form.save(commit=False)
candidate.user = UserProfile.objects.get(user=self.request.user) # use your own profile here
candidate.save()
return HttpResponseRedirect(self.get_success_url())
Assumptions
We don't want to set null=True becaues we don't want to allow null users at the model and/or database level
We don't want to set blank=True to mess with the readability of model because the user actually will not be blank
#nbm.ten solution is a good one. It has an advantages over other 'solutions'to this problem that utilized model to set the user (like this one) in nbm.ten's doesn't undermine the assumptions above. We don't want to mess with the model to fix a problem in view!
But here I add two other solutions based on django documentation (Models and request.user):
Two other solutions
1. Using the generic CreateView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView
from myapp.models import Candidate
class CandidateCreate(LoginRequiredMixin, CreateView):
model = Candidate
exclude = ['user']
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
2. Using class-based views
class CandidateForm(ModelForm):
class Meta:
model = Candidate
exclude = [ 'user',]
class CandidateAddView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
form = CandidateForm()
context = {'form':form}
return render(request, 'myapp/addcandidateview.html', context)
def post(self, request, *args, **kwargs):
form = CandidateForm(request.POST)
form.instance.user = request.user
if form.is_valid():
form.save()
return redirect(reverse('myapp:index'))
NOTES
Note that LoginRequiredMixin prevents users who aren’t logged in from accessing the form. If we omit that, we'll need to handle unauthorized users in form_valid() or post().
Also exclude = ['user'] prevents the user field to be shown on the form.
We used form.instance.user to set the user not form.data or form.cleaned_data they don't work
I have two, presumably related, problems with UpdateView. First, it is not updating the user but creating a new user object. Second, I cannot restrict the fields displayed in the form.
Here is my views.py:
class RegistrationView(FormView):
form_class = RegistrationForm
template_name = "register.html"
success_url = "/accounts/profile/"
def form_valid(self, form):
if form.is_valid:
user = form.save()
user = authenticate(username=user.username, password=form.cleaned_data['password1'])
login(self.request, user)
return super(RegistrationView, self).form_valid(form) #I still have no idea what this is
class UserUpdate(UpdateView):
model = User
form_class = RegistrationForm
fields = ['username', 'first_name']
template_name = "update.html"
success_url = "/accounts/profile/"
and urls.py
url(r'^create/$', RegistrationView.as_view(), name="create-user"),
url(r'^profile/(?P<pk>\d+)/edit/$', UserUpdate.as_view(), name="user-update"),
How do I properly use UpdateView?
Problem 1.
The user is not being updated because you are using the same form
(RegistrationForm) to do your updates and to create new users.
Problem 2. Forms belong in a file of their own called forms.py.
My suggested refactoring:
#forms.py
#place(forms.py) this in the same directory as views.py
class UpdateForm(forms.ModelForm):
#form for updating users
#the field you want to use should already be defined in the model
#so no need to add them here again DRY
class Meta:
model = User
fields = ('field1', 'field2', 'field3',)
#views.py
#import your forms
from .forms import UpdateForm
#also import your CBVs
from django.views.generic import UpdateView
class UserUpdate(UpdateView):
context_object_name = 'variable_used_in `update.html`'
form_class = UpdateForm
template_name = 'update.html'
success_url = 'success_url'
#get object
def get_object(self, queryset=None):
return self.request.user
#override form_valid method
def form_valid(self, form):
#save cleaned post data
clean = form.cleaned_data
context = {}
self.object = context.save(clean)
return super(UserUpdate, self).form_valid(form)
slightly elegant urls.py
#urls.py
#i'm assuming login is required to perform edit function
#in that case, we don't need to pass the 'id' in the url.
#we can just get the user instance
url(
regex=r'^profile/edit$',
view= UserUpdate.as_view(),
name='user-update'
),
You left out a lot of info so not really sure what your setup is.My solution is based on the assumption that you have Django 1.5. You can learn more about handling forms with CBVs
first: user = form.save() saves in the db the form. since there's no pk in the form it creates a new one.
what you have to do is probably to check if a user with that username exists and if not create it (for this part check google).
second: to restrict field you have to specify them in the Meta class of the Form (which you didn't show here) check this https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#modelform.
If you are getting new objects in the database instead of updating existing ones then it is likely that you copied and pasted the template for new objects and forgot to change the form's action attribute. This should point to view that does the update either in the form of a hard-coded path or a URL tag ({% url '<name of URLconf>' object.id %).
I'm trying to create an object in Django using the standard class based views and form libraries. Three fields in my form are dependent upon a domain variable captured from the URL pattern. My questions are:
How do I access domain within CreateSubscription so that I can set Subscription.site to Site.objects.filter(domain=domain)[0]?
How do I limit the dropdown fields rendered from CreateSubscriptionForm so that plan displays only SubscriptionPlan.objects.filter(site=Site.objects.filter(domain=domain)[0]) and payment_profile is limited to PaymentProfile.objects.filter(user=request.user)?
For clarity's sake, the domain in r'^subscribe/(?P<domain>\w+\.\w+)/$' is unrelated to my own site's domain. The django.contrib.sites framework won't help me here.
Thanks in advance for helping me untangle all of these CBV methods. :)
The URL pattern is:
from django.conf.urls import patterns, url
from accounts.views import *
url(r'^subscribe/(?P<domain>\w+\.\w+)/$',
CreateSubscription.as_view(), name='subscribe_to_site'),
)
The view in question is:
from accounts.forms import *
from accounts.models import *
class CreateSubscription(CreateView):
model = Subscription
form_class = CreateSubscriptionForm
def form_valid(self, form):
form.instance.user = self.request.user
return super(CreateSubscription, self).form_valid(form)
The relevant models are:
from django.conf import settings
from django.contrib.sites.models import Site
from django.db import models
class PaymentProfile(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
class SubscriptionPlan(models.Model):
site = models.ForeignKey(Site)
name = models.CharField(max_length=40)
class Subscription(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
site = models.ForeignKey(Site)
plan = models.ForeignKey(SubscriptionPlan)
payment_profile = models.ForeignKey(PaymentProfile)
And, finally, my form is:
from accounts.models import *
class CreateSubscriptionForm(forms.ModelForm):
class Meta:
model = Subscription
exclude = ('user', 'site', )
To access the data passed to the view, use self.args and self.kwargs
from accounts.forms import *
from accounts.models import *
class CreateSubscription(CreateView):
model = Subscription
form_class = CreateSubscriptionForm
def form_valid(self, form):
form.instance.user = self.request.user
form.instance.site = Site.objects.get(domain=self.kwargs['domain'])
return super(CreateSubscription, self).form_valid(form)
To restrict the dropdown content, you need to set the queryset for those fields. Very basically, something along these lines:
class CreateSubscriptionForm(forms.ModelForm):
plan = forms.ModelChoiceField(
queryset=SubscriptionPlan.objects.filter(xxx)
)
...
class Meta:
model = Subscription
exclude = ('user', 'site', )
This can also be done inside the view so that you have access to the domain.
class CreateSubscription(CreateView):
model = Subscription
form_class = CreateSubscriptionForm
def get_form(self, form_class):
"""
Returns an instance of the form to be used in this view.
"""
form = super(CreateSubscription, self).get_form(form_class)
form.fields['plan'].queryset = SubscriptionPlan.objects.filter(site__domain=self.kwargs['domain'])
return form
...
def form_valid(self, form):
form.instance.user = self.request.user
form.instance.site = Site.objects.get(domain=self.kwargs['domain'])
return super(CreateSubscription, self).form_valid(form)