django fill form field automatically from context data - django

I have a form attached to a DetailedView and its working fine when saved. I would like the form field(position) to be prepopulated with the value coming from the slug of the detailed view(e.g jobs/human-resource-manager). The Model of the form field has a Foreignkey to the JobPost model. Need help. Part of my view looks like this
class JobsDetailView(DetailView):
model = JobPost
template_name = 'job_post-detail.html'
def get_context_data(self, **kwargs):
context = super(JobsDetailView, self).get_context_data(**kwargs)
context['position'] = JobPost.objects.order_by('position')
context['job_app_form'] = JobsForm()
return context
foms.py
from django import forms
from job_post.models import JobsApplied
class JobsForm(forms.ModelForm):
class Meta:
model = JobsApplied
fields = '__all__'
def form_valid(self, form):
form.instance.customuser = self.request.user
return super().form_valid(form)

I'm assuming you do not want your users to be able to interact with or change these prefilled values.
I'm making a comments/review model and I want it to automatically link reviews to the people they are about
models.py
class Review(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
...
I hide the person field in the ReviewsForm to prevent user input by either omitting it from the 'fields' or adding it to an 'exclude'.
forms.py
class ReviewsForm(forms.ModelForm):
class Meta:
model = Review
fields = ('rating', 'summary', 'review_text')
Then, when processing the form in the view, I use commit=False so I can manipulate field values before saving to the database.
Include prefilled values, save and then redirect the user wherever is ideal
views.py
def person(request, area_slug, category_slug, person_id):
...
if form.is_valid():
pending_review = form.save(commit=False)
pending_review.person = Person.objects.get(pk = person_id)
pending_review.save()
return HttpResponseRedirect(...)

django fill form field automatically from context data for django form and django formsets
For formsets in forms.py
StoreRequestAccessoryUpdateFormSet = forms.modelformset_factory(StoreRequestAccessory, form=StoreRequestAccessoryUpdateForm, exclude=["storeRequestId"], can_delete=True)
In get_context_data you can add it as you like for django
class StoreRequestUpdateView(LoginRequiredMixin, UpdateView):
template_name = "Inventory/Stock/StoreRequest/StoreRequestUpdateView.html"
model = StoreRequest
fields = ["fromStoreId", "toStoreId", "reference", "status", "remark"]
def get_context_data(self, **kwargs):
context = super(StoreRequestUpdateView, self).get_context_data(**kwargs)
print(self.object.pk)
context.update({
# "StoreRequestForm": context.get("form"),
"StoreRequestForm": StoreRequestUpdateForm(instance=StoreRequest.objects.get(id=self.object.pk)),
"StoreRequestAccessoryForm": StoreRequestAccessoryUpdateFormSet(
queryset=StoreRequestAccessory.objects.filter(storeRequestId=self.object.pk),
prefix="storereq_accessory_form"),
})
return context

Related

How to get object uid from previous page in Django web-app?

I`m trying to make a CreateView form that takes the UID of the object as a foreign key from the previous page.
Here I got DetailView of Plan model:
class PlanDetailView(IsStaffPermissionMixin, DetailView):
model = Plan
template_name = "backoffice/plans/plan_detail.html"
context_object_name = 'plan'
def get_object(self):
uid = self.kwargs.get("uid")
return get_object_or_404(Plan, uid=uid)
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
practice_sessions = PracticeSession.objects.all().filter(plan__uid=self.kwargs.get("uid"))
context['practice_sessions'] = practice_sessions
return context
And here I got a PracticeSession CreateView:
class PracticeSessionCreateView(IsStaffPermissionMixin, CreateView):
model = PracticeSession
template_name = "backoffice/practice_session/practice_session_create.html"
fields = ['uid', 'title', 'plan', 'snippet', 'welcome_content', 'done_content', 'order_index', ]
success_url = reverse_lazy('practice-sessions')
As you understand, PracticalSession contains Foreign Key for the Plan model
Now I want that when I click on the button to create a PracticalSession (the picture below), I create a form in which the plan field already contains a "uid" of Plan from the page of which I create a new PracticalSession
My Form:
class PracticeSessionCreateForm(ModelForm):
class Meta:
model = PracticeSession
fields = '__all__'
Big THANK YOU In advance !!!

overwrite form for django CreateView

Perhaps just another stupid beginner question:
I am having trouble overwriting a django model form which is currently using the following code in views.py.
The code below works well !
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'content']
### want this instead of line 1 and 2 above --> form_class = PostCreateForm()
view_args = collections.namedtuple('view_args', ['page_title'])
view_args = view_args(page_title="Create Post")
def get_context_data(self, *args, **kwargs):
self.tgt_url_args = MyHelper.parse_tgt_url(self)
context = super(PostCreateView, self).get_context_data(*args, **kwargs)
context.update(MyHelper.get_context_metadata(self, self.tgt_url_args, PostCreateView.view_args))
return context
def form_valid(self, form):
form.instance.author = self.request.user
tgt_url_args = MyHelper.parse_tgt_url(self)
blog_article = Article.objects.get(pk=tgt_url_args.get('Article', '0'))
form.instance.article_field = blog_article
return super().form_valid(form)
However, I would like to overwrite the form in order to add placeholder to the fields. So I replace line 1 and 2 by:
form_class = PostCreateForm()
I also added the follwing in forms.py:
from django import forms
from . models import Post
class PostCreateForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'content']
print('imported PostCreateForm')
When I import and run this however, I get the following error:
'PostCreateForm' object is not callable
What am I doing wrong here?
You should not pass a constructed form, only a reference to the form class:
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
form_class = PostCreateForm # no parenthesis
# ...
Django will thus each time construct a new form (depending on the situation with request.POST, etc.).
If you need to pass extra parameters to the form, you can specify a dictionary in get_form_kwargs [Django-doc].

Using an inline formset in generic update view for editing related objects via a foreign key

It's clear from the django docs how to use an inline formset in a view.
But what if, following an example in the docs, you'd like to edit books related to an author via an Authors generic update view.
Is there a better or alternative approach to the following:
Create additional formset and add it to a context in get_context_data() method of a generic update view.
Deal with this additional formset in a form_valid() method of a generic update view - check if it's valid and save or update the context and return back if it's not.
models.py
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
views.py
class AuthorUpdate(UpdateView):
model = Author
fields = '__all__'
def get_context_data(self, **kwargs):
data = super(AuthorUpdate, self).get_context_data(**kwargs)
BooksFormSet = inlineformset_factory(Author, Book, fields='__all__', extra=1)
if self.request.POST:
# Create a formset instance to edit an existing model object,
# but use POST data to populate the formset.
data['books'] = BooksFormSet(self.request.POST, instance=self.get_object())
else:
# Create a formset with the data from model object and add it to context
data['books'] = BooksFormSet(instance=self.get_object())
return data
def form_valid(self, form):
context = self.get_context_data()
books = context['books']
with transaction.atomic():
self.object = form.save()
if books.is_valid():
books.instance = self.object
books.save()
else:
context.update({'books': books})
return self.render_to_response(context)
return super(AuthorUpdate, self).form_valid(form)

Django UpdateView - get initial data from many-to-many field and add them when saving form

I'm using Django class based generic view. In my models.py I have a model called MyModel with many-to-many field called m2m. I have multiple groups of users they can edit the m2m field. Each group of users can only see and add their portion to the field - using get_form to set what they can see in the m2m field. The problem I'm having is that when one user enter his record it will delete the initial records in the m2m field. I need to somehow get the initial values from the m2m field save them and then add them to the new ones when the form is submitted. Here is my views.py:
class MyModelUpdate(UpdateView):
model = MyModel
fields = ['m2m']
def get_initial(self):
return initials
def get_form(self, form_class=None):
form = super(MyModelUpdate, self).get_form(form_class)
form.fields["m2m"].queryset = DiffModel.objects.filter(user = self.request.user)
return form
def form_valid(self, form):
form.instance.m2m.add( ??? add the initial values)
return super(MyModelUpdate, self).form_valid(form)
def get_success_url(self):
...
I'm adding this answer to offer a simplified explanation of this kind of problem, and also because the OP switches from an UpdateView to a function based view in his solution, which might not be what some users are looking for.
If you are using UpdateView for a model that has a ManyToMany field, but you are not displaying it to the user because you just want this data to be left alone, after saving the form all the m2m values will be erased.
That's obviously because Django expects this field to be included in the form, and not including it is the same as just sending it empty, therefore, to tell Django to delete all ManyToMany relationships.
In that simple case, you don't need to define the form_valid and then retrieve the original values and so on, you just need to tell Django not to expect this field.
So, if that's you view:
class ProjectFormView(generic.UpdateView):
model = Project
form_class = ProjectForm
template_name = 'project.html'
In your form, exclude the m2m field:
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
fields = '__all__'
exclude = ['many_to_many_field']
after few days of searching and coding I've found a solution.
views.py:
from itertools import chain
from .forms import MyForm,
def MyModelUpdate(request, pk):
template_name = 'mytemplate.html'
instance = MyModel.objects.get(pk = pk)
instance_m2m = instance.m2m.exclude(user=request.user)
if request.method == "GET":
form = MyForm(instance=instance, user=request.user)
return render(request, template_name, {'form':form})
else:
form = MyForm(request.POST or None, instance=instance, user=request.user)
if form.is_valid():
post = form.save(commit=False)
post.m2m = chain(form.cleaned_data['m2m'], instance_m2m)
post.save()
return redirect(...)
forms.py:
from django import forms
from .models import MyModel
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ['m2m']
def __init__(self, *args, **kwargs):
current_user = kwargs.pop('user')
super(MyForm, self).__init__(*args, **kwargs)
self.fields['m2m'].queryset = self.fields['m2m'].queryset.filter(user=current_user)

How can I change the queryset of one of the fields in the form I'm passing to inlineformset_factory

I'm using django extra views:
# views.py
from django.forms.models import inlineformset_factory
from extra_views import (CreateWithInlinesView, UpdateWithInlinesView,
InlineFormSet, )
class LinkInline(InlineFormSet):
model = Link
form = LinkForm
extra = 1
def get_form(self):
return LinkForm({})
def get_formset(self):
return inlineformset_factory(self.model, self.get_inline_model(), form=LinkForm, **self.get_factory_kwargs())
class TargetCreateView(BaseSingleClient, CreateWithInlinesView):
model = Target
form_class = TargetForm
inlines = [LinkInline, ]
template_name = 'clients/target_form.html'
I want this 'keywords' field to change based on the pk I pass to the view through the url.
# forms.py
class LinkForm(forms.ModelForm):
keywords = forms.ModelMultipleChoiceField(queryset=ClientKeyword.objects.filter(client__pk=1))
class Meta:
model = Link
I could manage to overwrite the form's init, however:
I don't have access to self.kwargs inside LinkInline
Even if I did, I'm not sure I can pass an instantiated form to inlineformset_factory()
Ok, if any poor soul needs an answer to how to accomplish this... I managed to do it by overwriting construct_inlines() (which is part of extra_views.advanced.ModelFormWithInlinesMixin) and modifying the field's queryset there.
class TargetCreateView(BaseSingleClient, CreateWithInlinesView):
model = Target
form_class = TargetForm
inlines = [LinkInline, ]
template_name = 'clients/target_form.html'
def construct_inlines(self):
'''I need to overwrite this method in order to change
the queryset for the "keywords" field'''
inline_formsets = super(TargetCreateView, self).construct_inlines()
inline_formsets[0].forms[0].fields[
'keywords'].queryset = ClientKeyword.objects.filter(
client__pk=self.kwargs['pk'])
return inline_formsets
def forms_valid(self, form, inlines):
context_data = self.get_context_data()
# We need the client instance
client = context_data['client_obj']
# And the cleaned_data from the form
data = form.cleaned_data
self.object = self.model(
client=client,
budget=data['budget'],
month=data['month']
)
self.object.save()
for formset in inlines:
f_cd = formset.cleaned_data[0]
print self.object.pk
link = Link(client=client,
target=self.object,
link_type=f_cd['link_type'],
month=self.object.month,
status='PEN',
)
# save the object so we can add the M2M fields
link.save()
for kw in f_cd['keywords'].all():
link.keywords.add(kw)
return HttpResponseRedirect(self.get_success_url())