Django: Create two models with a CreateView - django

I have a CreateView for creating a customer, but I also need to create an 'identification' model along with this customer. I have an identification model that has a foreign key to the model because we need to be able to add any amount of IDs to some (Drivers license, Passport, etc)
Anyways, the current code (Which only creates a new customer) looks like this:
class CustomerCreationView(CreateView):
template_name = "customers/customer_information.html"
form_class = CustomerInformationForm
def get_context_data(self, *args, **kwargs):
context_data = super(CustomerCreationView, self).get_context_data(*args, **kwargs)
context_data.update({
'new_customer': True,
})
return context_data
CustomerInformationForm is ModelForm. I would like to create another ModelForm for Identifications, but I do not know how to add the second form to a CreateView. I found this article, but it is 5 years old and not talking about a CreateView.

You could use CreateWithInlinesView from django-extra-views. The code would look like this:
from extra_views import CreateWithInlinesView, InlineFormSet
class IdentificationInline(InlineFormSet):
model = Identification
class CustomerCreationView(CreateWithInlinesView):
model = CustomerInformation
inlines = [IdentificationInline]

class CustomerCreationView(CreateView):
template_name = "customers/customer_information.html"
form_class = CustomerInformationForm
other_form_class = YourOtherForm
def get_context_data(self, *args, **kwargs):
context_data = super(CustomerCreationView, self).get_context_data(*args, **kwargs)
context_data.update({
'new_customer': True,
'other_form': other_form_class,
})
return context_data

Related

Django - dynamic model for TemplateView class-based

I want to switch between two models depending on which route I am in.
I am overwriting get_queryset() function to return the correct model:
class DynamicModelView(TemplateView, PageDescriptionListingMixin):
model = None
template_name = 'dynamic_model.html'
context_object_name = "accounts"
def get_context_data(self, **kwargs):
context = super(DynamicModelView, self).get_context_data(**kwargs)
self.add_page_text_to_context(context)
return context
def get_queryset(self):
if '/dynamic_user/' in self.request.path:
model = UserAccount
else:
model = AdminAccount
return model.objects.first()
As you can see in get_context_data I am injecting an object in context for AdminAccount but inside template I can't see it! in fact if I changed model from None to AdminAccount then it appears which I want that to happen dynamically.
Is there any way to switch models dynamically in Django?
override dispatch method.
def dispatch(self, request, *args, **kwargs):
if not self.model:
self.model = <MODEL>
return super(DynamicModelView, self).dispatch(request, *args, **kwargs)

Creating a generic search view in Django

I am struggling to create my custom generic view in django to easily create search pages for certain models. I'd like to use it like this:
class MyModelSearchView(SearchView):
template_name = 'path/to/template.html'
model = MyModel
fields = ['name', 'email', 'whatever']
which will result in a view that returns a search form on GET and both form and results on POST.
The fields specifies which fields of MyModel will be available for a user to search.
class SearchView(FormView):
def get_form(self, form_class=None):
# what I'v already tried:
class SearchForm(ModelForm):
class Meta:
model = self.model
fields = self.fields
return SearchForm()
def post(self, request, *args, **kwargs):
# perform searching and return results
The problem with the code above is that form will not be submitted if certain fields are not be properly filled. User should be allowed to provide only part of fields to search but with the code I provided the form generated with ModelForm prevents that (for example because a field in a model cannot be blank).
My questions are:
Is it possible to generate a form based on a model to omit this behaviour?
Or is there any simpler way to create SearchView class?
I don't want to manually write forms if it's possible.
One way to accomplish this is to set blank=True on the field in MyModel, as indicated in the docs:
If the model field has blank=True, then required is set to False on the form field. Otherwise, required=True.
But for this to be a generic solution, you can't count on being able to modify the model fields. You can instead set the fields' required attribute to False immediately after the instance is created:
class SearchForm(ModelForm):
class Meta:
model = self.model
fields = self.fields
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for (field_name, field) in self.fields.items():
field.required = False
Since you're using the ModelForm for searching, you should set all the fields as required=False, by overriding the __init__ method:
def get_form(self, form_class=None):
# what I'v already tried:
class SearchForm(ModelForm):
class Meta:
model = self.model
fields = self.fields
def __init__(self, *args, **kwargs):
super(SearchForm, self).__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].required = False
return SearchForm()
Though I suggest you should user django-filter, which makes it easier and cleaner to filter your searches. First you need to install it:
pip install django-filter
Then add it to your INSTALLED_APPS. After that you can create a filters.py file in your app:
# myapp/filters.py
import django_filters as filters
from .models import MyModel
MyModelFilterSet(filters.FilterSet):
class Meta:
model = MyModel
fields = ['name', 'email', 'whatever']
By default it's going to filter with the __exact lookup. You can change this in a couple of ways, just take a look here and here. To know which lookups you can use, take a look here.
After creating your filters.py file you can add it to a View, like a ListView:
# myapp/views.py
from django.views.generic import ListView
from .filters import MyModelFilterSet
from .models import MyModel
class MyModelSearchView(ListView):
template_name = 'path/to/template.html'
model = MyModel
def get_queryset(self):
qs = self.model.objects.all()
filtered_model_list = MyModelFilterSet(self.request.GET, queryset=qs)
return filtered_model_list.qs
There's a lot more you can do with django-filter. Here's the full documentation.

Show a paginated ListView and an UpdateView on the same template page

I am trying to create a Django page where something can be updated and something can be viewed in a paginated table. The model looks like this:
class CostGroup(models.Model):
name = models.CharField(max_length=200)
description = models.CharField(max_length=200)
def get_absolute_url(self):
return reverse(
'costgroup_detail',
kwargs={
'costgroup_pk': self.pk,
}
)
class Cost(models.Model):
cost_group = models.ForeignKey(CostGroup)
amount = models.DecimalField(max_digits=50, decimal_places=2)
def get_absolute_url(self):
return reverse(
'cost_detail',
kwargs={
'cost_pk': self.pk,
}
)
So the edit form is for the name and description fields of the CostGroup model and the table should show a list of the 'amounts`
I previously had it working by just having an UpdateView for the form and the table included in the form template. Now though, as I want to include pagination on the table, I need to use two views on the same page. The page I have designed should look something like this in the end:
I am not worried about the styling at the moment my main focus at the moment is getting the form and the table on the same page. In its current state the only thing that I don't have is the pagination for the table:
The view currently looks like this:
class CostDetail(UpdateView):
model = models.Cost
pk_url_kwarg = 'cost_pk'
template_name = 'main/cost_detail.html'
form_class = forms.CostDetailEditForm
success_url = reverse_lazy('cost_list')
I have a feeling that leveraging the underlying mixins that the Django CBVs use is probably the way to go but I am not sure how to begin with this.
Any help would be much appreciated
Thanks for your time
(This clarification seemed to work better as a new answer)
It looks like you're dealing with both of the tables. The object level is using CostGroup, while the List view is showing the child records from Cost linked to a CostGroup. Assuming that is true, here's how I would proceed:
class CostDetail(ModelFormMixin, ListView):
model = CostGroup # Using the model of the record to be updated
form_class = YourFormName # If this isn't declared, get_form_class() will
# generate a model form
ordering = ['id']
paginate_by = 10
template_name = 'main/cost_detail.html' # Must be declared
def get_queryset(self):
# Set the queryset to use the Cost objects that match the selected CostGroup
self.queryset = Cost.objects.filter(cost_group = get_object())
# Use super to add the ordering needed for pagination
return super(CostDetail,self).get_queryset()
# We want to override get_object to avoid using the redefined get_queryset above
def get_object(self,queryset=None):
queryset = CostGroup.objects.all()
return super(CostDetail,self).get_object(queryset))
# Include the setting of self.object in get()
def get(self, request, *args, **kwargs):
# from BaseUpdateView
self.object = self.get_object()
return super(CostDetail,self).get(request, *args, **kwargs)
# Include the contexts from both
def get_context_data(self, **kwargs):
context = ModelFormMixin.get_context_data(**kwargs)
context = ListView.get_context_data(**context)
return context
# This is the post method found in the Update View
def post(self, request, *args, **kwargs):
# From BaseUpdateView
self.object = self.get_object()
# From ProcessFormView
form = self.get_form()
self.form = form
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def put(self, *args, **kwargs):
return self.post(*args, **kwargs)
I haven't tried to run this, so there may be errors. Good luck!
(Remember ccbv.co.uk is your friend when digging into Class-based Views)
An app I'm working on now uses a similar approach. I start with the ListView, bring in the FormMixin, and then bring in post() from the FormView.
class LinkListView(FormMixin, ListView):
model = Link
ordering = ['-created_on']
paginate_by = 10
template_name = 'links/link_list.html'
form_class = OtherUserInputForm
#=============================================================================#
#
# Handle form input
#
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form = self.get_form()
self.form = form
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def put(self, *args, **kwargs):
return self.post(*args, **kwargs)
def get_success_url(self):
return reverse('links')
You may also wish to override get_object(), get_queryset(), and get_context().

Django model formset performance

I have little perfomance issue with Django 1.4.2 and PostgreSQL 9.1. I want to create model fromset with form created like this:
forms.py
class AcknowledgeForm(forms.ModelForm):
class Meta:
model = Attendance
fields = ['acknowledge', ]
widgets = {'acknowledge': forms.CheckboxInput()}
AcknowledgeFormset = forms.models.modelformset_factory(Attendance, form=AcknowledgeForm, extra=0)
for model with few ForeignKeys
models.py
class Attendance(models.Model): #500k rows in DB
zamestnani = models.ForeignKey('people.Zamestnani', related_name='attendance') #~1k rows
day = models.ForeignKey(Day) #~2.5k rows
acknowledge = models.NullBooleanField(blank=True, null=True)
views.py
class VacationAcknowledgeView(LoginRequiredMixin, TemplateView):
template_name = "presence/presence_vacation_acknowledge.html"
http_method_names = ['get', 'post']
def get_context_data(self, **kwargs):
context = super(VacationAcknowledgeView, self).get_context_data()
person = Person.fromRequest(self.request)
first_day = date(date.today().year, 1, 1)
days = Attendance.objects.filter(acknowledge=None, day__date__gte=first_day, zamestnani__osoba=person)
context['formset'] = AcknowledgeFormset(queryset=days)
return context
def post(self, request, *args, **kwargs):
#next line is screwed
formset = AcknowledgeFormset(request.POST)
#never been there....
return super(VacationAcknowledgeView, self).get(request, *args, **kwargs)
I can create it, render it, everything seems ok, but assigning data from POST leads to server freeze for very looooong time (litteraly hours) for single object.
After short digging around I personaly blame model formset, because when I create it and process it just like single form, everything working as expected. But I have no idea how to fix/evade this.
Thank for any reasonable advice.
You mentioned that there are ~500,000 rows in your database for the Attendance models. In Django, when the queryset parameter is not specified for a ModelFormSet, it includes all objects in that model. Django is probably fetching all 500,000 rows of data.
You need to figure out the queryset for your ModelFormSet. E.g.
def post(self, request, *args, **kwargs):
queryset = Attendance.objects.none()
formset = AcknowledgeFormset(queryset=queryset, data=request.POST)
# continue with your regular code execution
The documentation contains a section for the queryset of a ModelFormSet: https://docs.djangoproject.com/en/1.9/topics/forms/modelforms/#django.forms.models.BaseModelFormSet

Django: Current User Id for ModelForm Admin

I want for filter a ModelChoiceField with the current user. I found a solution very close that I want to do, but I dont understand
Django: How to get current user in admin forms
The answer accepted says
"I can now access the current user in my forms.ModelForm by accessing self.current_user"
--admin.py
class Customer(BaseAdmin):
form = CustomerForm
def get_form(self, request,obj=None,**kwargs):
form = super(Customer, self).get_form(request, **kwargs)
form.current_user = request.user
return form
--forms.py
class CustomerForm(forms.ModelForm):
default_tax = forms.ModelChoiceField(queryset=fa_tax_rates.objects.filter(tenant=????))
class Meta:
model = fa_customers
How do I get the current user on modelchoice queryset(tenant=????)
How do I call the self.current_user in the modelform(forms.py)
Override __init__ constructor of the CustomerForm:
class CustomerForm(forms.ModelForm):
...
def __init__(self, *args, **kwargs):
super(CustomerForm, self).__init__(*args, **kwargs)
self.fields['default_tax'].queryset =
fa_tax_rates.objects.filter(tenant=self.current_user))
Queryset in the form field definition can be safely set to all() or none():
class CustomerForm(forms.ModelForm):
default_tax = forms.ModelChoiceField(queryset=fa_tax_rates.objects.none())
Just to sum up the solution because it was very hard for me to make this work and understand the accepted answer
In admin.py
class MyModelForm (forms.ModelForm):
def __init__(self, *args,**kwargs):
super (MyModelForm ,self).__init__(*args,**kwargs)
#retrieve current_user from MyModelAdmin
self.fields['my_model_field'].queryset = Staff.objects.all().filter(person_name = self.current_user)
#The person name in the database must be the same as in Django User, otherwise use something like person_name__contains
class MyModelAdmin(admin.ModelAdmin):
form = MyModelForm
def get_form(self, request, *args, **kwargs):
form = super(MyModelAdmin, self).get_form(request, *args, **kwargs)
form.current_user = request.user #get current user only accessible in MyModelAdminand pass it to MyModelForm
return form