Prepopulate ModelForm fields based on referring URL - python

I have a website which catalogs local hikes. Users can "log" that they have completed these hikes. I have both of these models+forms working as intended. Right now, though, in order to log a hike, you have to select the hike from a long list which contains all the hikes in the database. I'd like to be able to pre-populate that field so that if you are coming from the detail page of the hike in question, then that field is filled in with the hike.
Here's some code:
models.py:
model Hike(models.Model):
name = CharField(max_length=255)
slug = models.SlugField(unique=True)
...other fields...
model UserLog(models.Model):
hike = models.ForeignKey(Hike, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
forms.py:
class LogHike(forms.ModelForm):
class Meta:
model = UserLog
fields = ('hike', 'date', ... other fields)
views.py:
def hike_detail(request, slug):
hike = Hike.objects.get(slug=slug)
log = UserLog.objects.filter(hike__slug=slug)
... more stuff here ...
return render(request, 'hikes/hike_detail.html' {
'hike': hike,
'log': log,
})
def log_hike(request):
if request.method == "POST":
form = LogHike(request.POST)
if form.is_valid():
obj = form.save(commit=False)
userid = request.user
obj.user = request.user
obj.save()
return redirect('user_profile', uid=userid.id)
else:
form = LogHike()
return render(request, 'log_hike.html', {'form': form})
So if a user is viewing the "hike_detail" view, I want to have a link that sends them to the "log_hike" view, but with the "Hike" field pre-populated based on the "Hike" that they came from. I think it might have something to do with the instance function? But I don't have a ton of experience with it. Is there an easy way to pass the data from the referring page in order to pre-populate the form?

You probably want to override your ModelForm __init__ method:
def __init__(self, *args, **kwargs):
super(LogHike, self).__init__(*args, **kwargs)
if 'hike' in kwargs:
self.fields['hike'].value = kwargs['hike']
Now all you need is another view which accepts a parameter passed and you're set. Extend your urls.py for that and then do something like:
def log_hike_with_pre_set_hike(request, *args, **kwargs):
if request.method == 'POST':
# see your code
else:
form = LogHike(hike=kwargs['hike'])
return render(request, 'log_hike.html', {'form': form})
Untested code, you might have to adapt it, I come from class-based views so it might be different for you.

You can pre-populate that form in log_hike when the request.method is get in the same way as when in post.
form = LogHike({'hike':hike_id})
The other thing is form where you'll take the hike_id. But that can come from request.GET for example.

Related

Django - issue with Not Null constraint

Hi in my program I keep receiving the above exception and am unsure why. The issue happens when my requestLessons_view method tries to save the form.
Views.py
def requestLessons_view(request):
if request.method == 'POST':
form = RequestLessonsForm(request.POST)
if form.is_valid() & request.user.is_authenticated:
user = request.user
form.save(user)
return redirect('login')
else:
form = RequestLessonsForm()
return render(request, 'RequestLessonsPage.html', {'form': form})
forms.py
class RequestLessonsForm(forms.ModelForm):
class Meta:
model = Request
fields = ['availability', 'num_of_lessons', 'interval_between_lessons', 'duration_of_lesson','further_information']
widgets = {'further_information' : forms.Textarea()}
def save(self, user):
super().save(commit=False)
request = Request.objects.create(
student = user,
availability=self.cleaned_data.get('availability'),
num_of_lessons=self.cleaned_data.get('num_of_lessons'),
interval_between_lessons=self.cleaned_data.get('interval_between_lessons'),
duration_of_lesson=self.cleaned_data.get('duration_of_lesson'),
further_information=self.cleaned_data.get('further_information'),
)
return request
The error I receive is:
IntegrityError at /request_lessons/
NOT NULL constraint failed: lessons_request.student_id
Your .save() method is defined on the Meta class, not the form, hence the error. I would advise to let the model form handle the logic: a ModelForm can be used both to create and update the items, so by doing the save logic yourself, you basically make the form less effective. You can rewrite this to:
class RequestLessonsForm(forms.ModelForm):
class Meta:
model = Request
fields = [
'availability',
'num_of_lessons',
'interval_between_lessons',
'duration_of_lesson',
'further_information',
]
widgets = {'further_information': forms.Textarea}
def save(self, user, *args, **kwargs):
self.instance.student = user
return super().save(*args, **kwargs)
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].

Django: update a model with the current user when an non-model form field is changed

I'm building a page that allows users to edit Task and related Activity records (one task can have many activities), all on the same page. I want to allow the user to "adopt" one or more activities by ticking a box, and have their user record linked to each activity via a ForeignKey. Here are extracts from my code...
models.py
from django.contrib.auth.models import User
class Task(models.Model):
category = models.CharField(max_length=300)
description = models.CharField(max_length=300)
class Activity(models.Model):
task = models.ForeignKey(Task, on_delete=models.CASCADE)
title = models.CharField(max_length=150)
notes = models.TextField(blank=True)
owner = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
The activity "owner" is linked to a User from the Django standard user model.
I added an extra field in the form definition for the adopt field - I don't want to add it to the model as I don't need to save it once it's done it's job.
forms.py
class ActivityForm(forms.ModelForm):
adopt = forms.BooleanField(required=False)
class Meta:
model = Activity
fields = '__all__'
views.py
def manage_task(request, pk):
task = Task.objects.get(pk = pk)
TaskInlineFormSet = inlineformset_factory(Task, Activity,
form = ActivityForm)
if request.method == "POST":
form = TaskForm(request.POST, instance = task)
formset = TaskInlineFormSet(request.POST, instance = task)
if form.has_changed() and form.is_valid():
form.save()
if formset.has_changed() and formset.is_valid():
## ? DO SOMETHING HERE ? ##
formset.save()
return redirect('manage_task',pk=task.id)
else:
form = TaskForm(instance = task)
formset = TaskInlineFormSet(instance = task)
context = {'task': task, 'task_form': form, 'formset': formset}
return render(request, 'tasks/manage_task.html', context)
When the adopt field is ticked on the form, I want to be able to set the owner field in that form to the current user before the associated model instance is updated and saved.
I just can't figure out how to do that - if it was a single form (rather than an InlineFormSet), I think I could put code in the view to change the owner value in the form field before it was saved (I haven't tried this). Or try save(commit = False) and update the model instance then save() it.
Maybe I have to iterate through the formset in the view code and try one of those options when I find one that had adopt=True?
When the adopt field is ticked on the form, I want to be able to set the owner field in that form to the current user before the associated model instance is updated and saved.
formset = TaskInlineFormSet(request.POST, instance = task)
if formset.adopt:
# If True
formset.user = request.user
formset.save()
I think I could put code in the view to change the owner value in the form field before it was saved (I haven't tried this).
You should give it a try.
I'm not happy with this solution but it does work. I iterate through the forms and change the object instance if my adopt field is set.
views.py
def manage_task(request, pk):
task = Task.objects.get(pk = pk)
TaskInlineFormSet = inlineformset_factory(Task, Activity,
form = ActivityForm)
if request.method == "POST":
form = TaskForm(request.POST, instance = task)
formset = TaskInlineFormSet(request.POST, instance = task)
if form.has_changed() and form.is_valid():
form.save()
if formset.has_changed() and formset.is_valid():
## HERE'S WHAT I ADDED ##
for form in formset:
if form.cleaned_data['adopt'] is True:
form.instance.owner = request.user
## END OF ADDITIONS ##
formset.save()
## return redirect('manage_task',pk=task.id) # CHANGED THIS BECAUSE I WASN'T RETURNG ERRORS!
if not form.errors and not formset.total_error_count():
return redirect('manage_task',pk=task.id)
else:
form = TaskForm(instance = task)
formset = TaskInlineFormSet(instance = task)
context = {'task': task, 'task_form': form, 'formset': formset}
return render(request, 'tasks/manage_task.html', context)
I wish I could find more in the docs about how the form saving works but I think I'll have to look into the code if I want more detail.

Django; how to forward last form's details to the next? [duplicate]

I would like to create a mutli-step form in Django that only submits the data for processing at the end of all the steps. Each step needs to be able to access and display data that we entered in previous step(s).
Is there a way to do this with Django? Django's Form-Wizard can't handle this basic functionality.
Of course there's a way to do this in Django.
One way is to hold your values in session until you submit them at the end. You can populate your forms using values held in session if you return to previous step.
With some searching, you may find an app that someone has already written that will do what you want, but doing what you need isn't hard to do with Django, or any other framework.
Example, ignoring import statements:
#models/forms
class Person(models.Model):
fn = models.CharField(max_length=40)
class Pet(models.Model):
owner = models.ForeignKey(Person)
name = models.CharField(max_length=40)
class PersonForm(forms.ModelForm):
class Meta:
model = Person
class PetForm(forms.ModelForm):
class Meta:
model = Pet
exclude = ('owner',)
#views
def step1(request):
initial={'fn': request.session.get('fn', None)}
form = PersonForm(request.POST or None, initial=initial)
if request.method == 'POST':
if form.is_valid():
request.session['fn'] = form.cleaned_data['fn']
return HttpResponseRedirect(reverse('step2'))
return render(request, 'step1.html', {'form': form})
def step2(request):
form = PetForm(request.POST or None)
if request.method == 'POST':
if form.is_valid():
pet = form.save(commit=False)
person = Person.objects.create(fn=request.session['fn'])
pet.owner = person
pet.save()
return HttpResponseRedirect(reverse('finished'))
return render(request, 'step2.html', {'form': form})
We'll assume that step2.html has a link to go back to step1.html.
You'll notice in the step1 view I'm pulling the value for fn from session that was set when the form was saved. You would need to persist the values from all previous steps into the session. At the end of the steps, grab the values, create your objects and redirect to a finished view, whatever that might be.
None of this code has been tested, but it should get you going.
You can easily do this with the form wizard of django-formtools. A simple example would be something like the following.
forms.py
from django import forms
class ContactForm1(forms.Form):
subject = forms.CharField(max_length=100)
sender = forms.EmailField()
class ContactForm2(forms.Form):
message = forms.CharField(widget=forms.Textarea)
views.py
from django.shortcuts import redirect
from formtools.wizard.views import SessionWizardView
class ContactWizard(SessionWizardView):
def done(self, form_list, **kwargs):
do_something_with_the_form_data(form_list)
return redirect('/page-to-redirect-to-when-done/')
urls.py
from django.conf.urls import url
from forms import ContactForm1, ContactForm2
from views import ContactWizard
urlpatterns = [
url(r'^contact/$', ContactWizard.as_view([ContactForm1, ContactForm2])),
]

Django form in another template

New to Django, so please bear with me.
I've been fighting my way through the basic tutorial and documentation, but haven't found a satisfactory answer.
I have two models:
class Venue(models.Model):
name = models.CharField(max_length=200)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('venue_detail', kwargs={'pk': self.pk})
class Space(models.Model):
venue = models.ForeignKey('Venue', null=True)
name = models.CharField(max_length=200)
description = models.TextField(max_length=500)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('space_detail', kwargs={'pk': self.pk})
A space belongs to a Venue. That's all fine.
What I want to do, is to create a Space from a VenueDetail view, and use that Venue as the foreign key (i.e no dropdown select on the Space ModelForm).
However I haven't got as far as trying to use the current venue instance in the space form (is that called context? Sorry, coming from Rails), as I'm stuck on the form redirecting part.
views.py
class VenueDetail(FormView):
model = Venue
template_name = 'venues/detail.html'
form_class = SpaceForm
def get_context_data(self, **kwargs):
context = super(VenueDetail, self).get_context_data(**kwargs)
context['form'] = SpaceForm
return context
class SpaceCreate(CreateView):
model = Space
template_name = 'spaces/space_form.html'
fields = '__all__'
forms.py
class SpaceForm(ModelForm):
class Meta:
model = Space
fields = '__all__'
venue_urls.py
urlpatterns = [
url(r'^$', VenueListing.as_view(), name='venue_listing'),
url(r'^(?P<pk>[0-9]+)/$', VenueDetail.as_view(), name='venue_detail'),
url(r'^create/$', VenueCreate.as_view(), name='venue_create')
]
space_urls.py
urlpatterns = [
url(r'^$', SpaceListing.as_view(), name='space_listing'),
url(r'^(?P<pk>[0-9]+)/$', SpaceDetail.as_view(), name='space_detail'),
url(r'^create/$', SpaceCreate.as_view(), name='space_create')
]
If the user is creating a space from a VenueDetail view, at the moment if they have any errors they are being given the form with errors showing, but at the standard SpacesCreate url. Obviously this is happening because the form POSTs to that URL which then calls SpaceCreate.as_view(). Really the user would expect to be at the same URL if the form failed. If the form validates fine, at the moment it goes to the correct place which is 'space/pk' of the created space, which is fairly standard.
Essentially what I'm asking is what is the Django way to include a form to create one model, on it's parents show/detail page with all the expected behaviour. Perhaps I should be using a custom View for this form action when on that Venue detail page? One that specifically redirects to the same Venue detail page if the form post doesn't validate?
Thanks in advance for any help
UPDATE
I used a normal function view in the end, and ended up with something like this:
views.py
def new_space(request, venue_id):
venue = Venue.objects.get(id = venue_id)
context = {'form': SpaceForm, 'venue': venue}
if request.method == 'POST':
form = SpaceForm(request.POST)
if form.is_valid():
new_space = form.save(commit=False)
new_space.venue = venue
new_space.save()
return HttpResponseRedirect(reverse('space_listing'))
else:
return render(request, 'spaces/new_space.html', {'form': form})
else:
form = SpaceForm()
return render(request, 'spaces/new_space.html', context)
urls.py
url(r'^(?P<venue_id>[0-9]+)/new_space/$', views.new_space, name='new_space')
and all seems to be working well.

Django Admin class to auto-populate user field

I am trying to populate the field 'owner' in the my NoteForm. I read in documentation that I need to use the Admin for that.But i still get this error : note_note.owner_id may not be NULL. Need help. Code:
forms.py:
class NoteForm(forms.ModelForm):
class Meta:
model = Note
fields = ('title','body')
models.py:
class Note(models.Model):
title = models.CharField(max_length=200)
body = models.TextField()
cr_date = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(User, blank=False)
admin.py
class NoteAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
obj = form.save(commit=False)
obj.owner = request.user
obj.save()
def save_formset(self, request, form, formset, change):
instances = formset.save(commit=False)
for instance in instances:
instance.user = request.user
instance.save()
else:
fromset.save_m2m()
admin.site.register(Note, Noteadmin)
views.py:
def create(request):
if request.POST:
form = NoteForm(request.POST, request.FILES)
if form.is_valid():
form.save()
return HttpResponceRedirect('/notes/all')
else:
form = NoteForm()
args = {}
args.update(csrf(request))
args['form'] = form
return render_to_response('create_note.html', args)
I do not understand the point of Admin over here. From the code, what I understood is you creating a simple django form for your site and getting the error on form submission. If that's case, the solution is quiet easy. This error is generated because you are try to save a record in your Note model without any reference to User. As there's a db constraint on the foreign key field, it raises the error. Solution is easy, just add owner to the list of fields in the form or modify the save method to assign an owner to the note. If you'll use the first option, the user will be able to see and select the owner. And if you want to pre-populate that particular field, pass initial value to the form.

Categories

Resources