How to efficiently initial data in Django forms.Form? - django

For example I have a Article model for blog articles so it's easy to add articles to the database.
But when I need to edit them, in form if I create a form class by form.ModelForm, I can pass instace=artcile to the form and that's it.
But if I create a form class by form.Forms I have to declare a form instance and pass fields to the form one by one.
Something like this
form = ArticleForm({
'title': article.title,
'body': article.body,
'pub_date': article.pub_date
'status': article.status,
'author': article.author,
'comments': article.comments.count(),
'blah': article.blahblah,
'againBlah': article.againBlah,
.....
})
It's ugly, isn't?
Is there any way to do this shorter, without using form.ModelForm?

You can use the model_to_dict and fields_for_model utils from django.forms.models:
# assuming article is an instance of your Article model:
from django.forms import Form
from django.forms.models import fields_for_model, model_to_dict
form = Form(model_to_dict(article))
form.fields.update(fields_for_model(article))
If you have an m2m relation, you can create a formset for it:
from django import forms
from django.forms.models import model_to_dict, fields_for_model
from django.forms.formsets import formset_factory
# assuming your related model is called 'Tag'
class TagForm(forms.Form):
def __init__(self, *args, **kwargs):
super(TagForm, self).__init__(*args, **kwargs)
self.fields.update(fields_for_model(Tag))
TagFormSet = formset_factory(TagForm)
formset = TagFormSet(initial=[model_to_dict(tag) for tag in article.tags.all()])
Then you can iterate through the formset to access the forms created for the related models:
for form in formset.forms:
print form

Related

Is there any way to use bulk_create with model formsets?

I am currently using model formsets in my project.
The problem I find is that the app may have to show more than 50 forms on the same page, so saving them using the .save() method would generate more than 50 queries (one query for each form I'm going to save).
Since all forms have the same structure, it would be ideal to be able to save them with bulk_create, in such a way that only one query is generated, however the modelformset does not support bulk_create.
Is there any way to save all the answers of the forms with only one query?
The only thing that I can think of, is after validating the forms with formset.is_valid(), recover the request.POST and from there save with bulk_create.
Is there a better alternative?
I think you are very close to solution (But you can use form.cleaned_data also, not QueryDict). I will show you my implementation now (Which uses bulk_create() Manager/QuerySet method. This will help us to avoid a lot of hits on DB)
forms.py
class MyForm(forms.Form):
''' use same names for fields as you have in models.py '''
name = forms.CharField()
surname = forms.CharField()
models.py
class Person(models.Model):
name = models.CharField(max_length=55)
surname = models.CharField(max_length=55)
class Meta:
db_table = 'person'
views.py
from django.shortcuts import render
from django.http import JsonResponse
from django.forms import formset_factory
from django.db import connection
from .models import Person
from .forms import MyForm
N = 3 # number of forms
def index(request):
FormSet = formset_factory(MyForm, extra=N)
if request.method == 'POST':
fs = FormSet(data=request.POST)
if fs.is_valid():
data_for_bulk = [Person(**field_dict) for field_dict in fs.cleaned_data] # this returns list and pass to bulk_create() method.
Person.objects.bulk_create(data_for_bulk)
# use connection.queries to make monitoring of sql queries during HTTP POST request.
for query in connection.queries:
print(query['sql'], '\n')
return JsonResponse({'status_message': 'OK'})
else:
fs = FormSet()
return render(request, 'test.html', {'form': fs})
I hope, my solution will help you. Good luck !

AttributeError at has no attribute 'objects'

AttributeError type object 'Data_Point ' has no attribute 'objects'plz check and correct me
AttributeError at /
type object 'myProduction' has no attribute 'objects'
model":
from django.db import models
from django.contrib.auth.models import User
class Production(models.Model):
title=models.CharField(max_length=120)
def __str__(self):
return self.title
My Form
from django import forms
from.models import Production
class myProduction(forms.ModelForm):
class Meta:
model=Production
fields =['title']
class Raw_Pro(forms.Form):
title = forms.CharField()
My View
from django.shortcuts import render
from .form import myProduction,Raw_Pro
def my_index(request):
my_form=Raw_Pro()
if request.method=='POST':
my_form=Raw_Pro(request.POST)
if my_form.is_valid():
myProduction.objects.create(my_form.cleaned_data)
else:
print(my_form.errors)
context={"form":my_form}
return render(request, "index.html",context)
You make some mistakes here:
myProduction here is your ModelForm (defined in forms.py), not your model (this is Production, defined in `models.py);
you here use Raw_Pro as form, which is not a ModelForm, which is likely not what you want to use;
in case of a successful form, you can use mymodelform.save() to create/edit the object; and
if the creation is successful, you should redirect to a page, for example the same page. By not doing so, a refresh of the user, would trigger a POST with the same parameters.
from django.shortcuts import render
from .form import myProduction
def my_index(request):
if request.method == 'POST':
my_form = myProduction(request.POST)
if my_form.is_valid():
my_form.save()
return redirect(my_index) # or somewhere else
else:
my_form = myProduction()
context = {"form":my_form}
return render(request, "index.html",context)
Note: as specified by PEP-8 [Python-doc], you should use camelcase starting with an Uppercase for class names. So you better rename your myProduction class to MyProduction, or much better ProductionForm, since then it is clear what that class is doing.

HTTP forward from generic CreateView in Django?

I have a CreateView for a patient object (simplified):
from django.views.generic.edit import CreateView
import models
class PatientCreate(CreateView):
model = models.Patient
fields = ['name', 'country', ..]
# template_name is "patient_form.html" from CreateView
(I have overridden form_valid and get_context_data to set a few things by default, see for example here, but I think that's irrelevant.)
If a patient by the same name already exists, I'd like to simply HTTP forward to the detail page for that patient instead of creating a new one.
How do I do that?
I'm using Django 1.11.
You can add this logic in form_valid override. For example:
def form_valid(self, form):
name = form.cleaned_data.get('name')
your_model_objects = YourModel.objects.filter(name=name)
if your_model_objects.exists(): # lazy query, won't hit the database
obj = your_model_objects.first() # as entry exists, fetch the first object
return redirect(reverse('detail-url', args=[obj.pk])
else:
return super(YourClass, self).form_valid(form)

What's the difference between Django ModelForm and models.Model?

I am learning Django but cannot understand ModelForm ans models.Model, could anyone explain the difference between them? Thanks!
Adapted from django docs
A ModelForm is a Form that maps closely to the fields defined in a specific Model. Models are defined by subclassing models.Model.
A Model is the source of information about your data. It contains the essential fields and behaviours of the data you’re storing. Generally, each model maps to a single database table. When you are developing your webapp, chances are that you’ll have forms that map closely to Django models. For this reason, Django provides a helper class that lets you create a Form class from a Django model. This helper class is the ModelForm class
The ModelForms you create are rendered in your templates so that users can create or update the actual data that is stored in the database tables defined by your Models.
For instance lets say you want to store data about articles in your web application, you would first define a model called Article like so:
in your models.py:
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=30)
body = models.TextField(max_length=300)
in your forms.py you would create a modelForm to correspond with the Article model you have just created.
from .models import Article
from django.forms import ModelForm
class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ['title', 'body']
and then in your views.py you can render your articleform:
from django.shortcuts import render
from django.http import HttpResponseRedirect
from .forms import ArticleForm
def article_form_view(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = ArticleForm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
# ...
# redirect to a new URL:
return HttpResponseRedirect('/')
# if a GET (or any other method) we'll create a blank form
else:
form = ArticleForm()
return render(request, 'article_form.html', {'form': form})

How to add custom page to django admin with custom form, not related to any Model?

I want to bulk_create models by importing csv data through django admin, with TextArea or FileField. I learned how to override template blocks, how to add new urls to django admin. But I have no idea how to solve my problem. I want to create custom admin page with my form. Pass data, parse it and bulk_create my model objects. Can you guys suggest the way how can I do this?
I found a snippet for this situation
from django.contrib import admin, messages
from django.http import HttpResponseRedirect
from django.shortcuts import render
from my_app.forms import CustomForm
class FakeModel(object):
class _meta:
app_label = 'my_app' # This is the app that the form will exist under
model_name = 'custom-form' # This is what will be used in the link url
verbose_name_plural = 'Custom AdminForm' # This is the name used in the link text
object_name = 'ObjectName'
swapped = False
abstract = False
class MyCustomAdminForm(admin.ModelAdmin):
"""
This is a funky way to register a regular view with the Django Admin.
"""
def has_add_permission(*args, **kwargs):
return False
def has_change_permission(*args, **kwargs):
return True
def has_delete_permission(*args, **kwargs):
return False
def changelist_view(self, request):
context = {'title': 'My Custom AdminForm'}
if request.method == 'POST':
form = CustomForm(request.POST)
if form.is_valid():
# Do your magic with the completed form data.
# Let the user know that form was submitted.
messages.success(request, 'Congrats, form submitted!')
return HttpResponseRedirect('')
else:
messages.error(
request, 'Please correct the error below'
)
else:
form = CustomForm()
context['form'] = form
return render(request, 'admin/change_form.html', context)
admin.site.register([FakeModel], MyCustomAdminForm)
from django import forms
class CustomForm(forms.Form):
# Your run-of-the-mill form here
Using a proxy model would save some typing:
class ImportCSVData(SomeModel):
class Meta:
proxy = True
#admin.register(ImportCSVData)
class MyCustomAdminForm(admin.ModelAdmin):
... as in accepted answer ...
I'm glad to say that since version 1.3.0 django-etc ships with etc.admin.CustomModelPage. So you may want to do something like:
from etc.admin import CustomModelPage
class BulkPage(CustomModelPage):
title = 'Test page 1' # set page title
# Define some fields.
my_field = models.CharField('some title', max_length=10)
def save(self):
# Here implement bulk creation using values
# from self fields attributes, e.g. self.my_field.
super().save()
# Register this page within Django admin.
BulkPage.register()
When I came across this answer, I was hoping to find a way to add a second form to the admin page for an existing model. The answer here gets you sort of close, but there is a much easier way to approach this.
For this example, I will assume the model we're working with is called Candle.
# Make a proxy class for your model, since
# there can only be one admin view per model.
class EasyCandle(models.Candle):
class Meta:
proxy = True
# Make a ModelAdmin for your proxy class.
#admin.register(EasyCandle)
class EasyCandleAdminForm(admin.ModelAdmin):
# In my case, I only want to use it for adding a new model.
# For changing an existing instance of my model or deleting
# an instance of my model, I want to just use the
# views already available for the existing model.
# So has_add_permission returns True while the rest return False.
def has_add_permission(*args, **kwargs):
return True
def has_change_permission(*args, **kwargs):
return False
def has_delete_permission(*args, **kwargs):
return False
# This replaces all the complicated stuff other
# answers do with changelist_view.
def get_form(self, request, obj=None, **kwargs):
return EasyCandleForm
# Finally, make whatever form you want.
# In this case, I exclude some fields and add new fields.
class EasyCandleForm(forms.ModelForm):
class Meta:
model = models.Candle
# Note, do NOT exclude fields when you want to replace their form fields.
# If you do that, they don't get persisted to the DB.
fields = "__all__"
vessel = forms.CharField(
required=True,
help_text="If the vessel doesn't already exist in the DB, it will be added for you",
)