Django: Accessing URL variables in Class Based Views and Forms - django

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)

Related

How to set ForeignKey in the model depending on a slug?

I have a registration form for an event. Since this registration form displays as a modal when clicking the 'Register' button on the event page, I know what event it is that the user want to register to. But Django doesn't, since I don't know how to implement this in code.
I have two models: Participant and Event. Each instance of Participant refers to an Event instance by means of ForeignKey. How do I set that ForeignKey depending on the slug of the event page?
This is my code example:
models.py:
from django.db import models
class Event(models.Model):
name = models.CharField(max_length=50)
description = models.CharField(max_length=500)
#<...>
slug = models.SlugField()
class Participant(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField()
event = models.ForeignKey(Event, on_delete=models.CASCADE)
forms.py:
from django.forms import ModelForm
from .models import Participant
class ParticipantForm(ModelForm):
class Meta:
model = Participant
fields = ['name', 'email']
views.py:
from django.template.loader import render_to_string
from django.views import generic
from .models import *
from .forms import *
class RegistrationView(generic.FormView):
template_name = 'me/registration.html'
form_class = ParticipantForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['event'] = Event.objects.get(slug=self.args[0])
return context
def form_valid(self, form):
form.save()
return HttpResponse(render_to_string('me/registration-complete.html', {'event': Event.objects.get(slug=self.args[0])}))
You'd need to set it in form_valid. In this circumstance get_context_data wouldn't have been called, so you need to get the event again; you might want to extract that into a separate method.
def form_valid(self, form):
form.instance.event = Event.objects.get(reference_name=self.args[0])
form.save()
return ...

NOT NULL constraint failed: history.user_id

I am new to Django and I am trying to learn by practicing with some project but I am stuck with this problem, I want to return the information of the History model to the authenticated user according to its id_user. The problem appears when the user gives the submit of the form.
The message that he gives me is this:
NOT NULL constraint failed: history.user_id
models.py
from django.db import models
from django.contrib.auth.models import User
class History(models.Model):
DAY35 = '35 days'
DAY45 = '45 days'
HISTORY_DAYS = (
(DAY35, '35 days'),
(DAY45, '45 days'),
)
user = models.ForeignKey(User, on_delete=models.CASCADE)
amount = models.FloatField(default=10)
days = models.CharField(
max_length=7,
choices=HISTORY_DAYS,
default=DAY35,
)
def is_upperclass(self):
return self.days in (self.DAY35, self.DAY45)
views.py
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
from django.views.generic.edit import CreateView
from .forms import HistoryForm
from .models import History
#method_decorator(login_required, name='dispatch')
class HistoryCreate(CreateView):
model = History
fields = ['amount', 'days']
success_url = reverse_lazy('history')
form_class = HistoryForm
forms.py
from django import forms
from .models import MiningHistory
class HistoryForm(forms.ModelForm):
class Meta:
model = History
fields = ['amount', 'days']
widgets = {
'amount': forms.NumberInput(attrs={'class':'x', 'placeholder':'0.00'}),
'days': forms.Select(attrs={'class':'x'}),
}
A History instance must have a non null user field. However you are not specifying the user related the History object you're creating.
In case you dont want to add the user, update you're model's user field :
user = models.ForeignKey(User, on_delete=models.CASCADE,blank=True,null=True)
If you want to associate the user field with the logged in user, update your views :
#method_decorator(login_required, name='dispatch')
class HistoryCreate(CreateView):
model = History
fields = ['amount', 'days']
success_url = reverse_lazy('history')
form_class = HistoryForm
def form_valid(self, form_class ):
form_class.instance.user= self.request.user
return super().form_valid(form)
Don't forget to add user to your form fields.
PS : Don't add user to your views modifiable fields. Check this for more details.

Django - Save forms in the same data table

I'm having problem in saving selected choices in the same row of data table for each participant).
I have 2 forms. The first form is for some data and a multiple-choice question. The second form is another multiple-choice question. It also means I have 2 pages, after submit answers for page 1, it will redirect to page 2 for question 2.
When I try to be a participant and choose answers, the selected choices are saved in different data table. Pictures and code are displayed below.
forms.py
from django import forms
from .models import Survey
class SurveyForm(forms.ModelForm):
BAT='Batman'
SUPER='Superman'
IRON='Ironman'
WHATMOVIE1 = [
(BAT, 'Batman'),
(SUPER, 'Superman'),
(IRON, 'Ironman'),
]
movie_1 = forms.ChoiceField(choices=WHATMOVIE1, widget=forms.RadioSelect())
class Meta:
model = Survey
fields = ["location", "age",
"marital_status", "education", 'movie_1']
class SurForm(forms.ModelForm):
APP='Apple'
BAN='Banana'
LEM='Lemon'
WHATMOVIE2 = [
(APP, 'Apple'),
(BAN, 'Banana'),
(LEM, 'Lemon'),
]
movie_2 = forms.ChoiceField(choices=WHATMOVIE2, widget=forms.RadioSelect())
class Meta:
model = Survey
fields = [ 'movie_2']
views.py
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from .forms import SurveyForm, SurForm
def homepage(request):
if request.method == 'POST':
title = "Questionnaire"
form = SurveyForm(request.POST or None)
if form.is_valid():
instance = form.save(commit=False)
instance.save()
return HttpResponseRedirect(reverse('nextpage'))
else:
form = SurveyForm()
return render(request, "homepage.html", {"form": form})
def nextpage(request):
title = "Next page"
form = SurForm(request.POST or None)
if form.is_valid():
instance = form.save(commit=False)
instance.save()
context = {
"form": form,
}
return render(request, "nextpage.html", context)
admin.py
from django.contrib import admin
from .forms import SurveyForm, SurForm
from .models import Survey, Sur
class SurAdmin(admin.ModelAdmin):
form = SurForm
class SurveyAdmin(admin.ModelAdmin):
list_display = ["location", "age",
"marital_status", "education",
"movie_1"]
form = SurveyForm
admin.site.register(Survey, SurveyAdmin)
admin.site.register(Sur, SurAdmin)
What should I do to save all selected answers in the same row for each participant?
You are using two different models, that is why they are getting separated. You need to create a relation between them, using a OneToOneField can solve the problem. Something like this:
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
def __str__(self): # __unicode__ on Python 2
return "%s the place" % self.name
class Restaurant(models.Model):
place = models.OneToOneField(Place, primary_key=True)
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
def __str__(self): # __unicode__ on Python 2
return "%s the restaurant" % self.place.name
Optionally, you can just combine the two models.

Set ForeignKey value from url parameter in Django's CreateView

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)

How do I use UpdateView?

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 %).