I tried creating a generic UpdateView for a model with generic relations with 2 other models, based on the CreateView provided in this tutorial.
The form and formsets are populated as expected, but when I save I get redirected to the model detail page and changes are not saved.
My question is similar to this one but the suggestions provided are not helpful because they are specific to the former question.
Here is my code :
# models.py
class Ingredient(models.Model):
...
content_type = models.ForeignKey(ContentType, editable=False)
object_id = models.PositiveIntegerField(editable=False)
content_object = GenericForeignKey()
class Step(models.Model):
Same as Ingredient
class Recipe(models.Model):
...
ingredients = generic.GenericRelation(Ingredient)
steps = generic.GenericRelation(Step)
# forms.py
class RecipeForm(forms.ModelForm):
class Meta:
model = Recipe
IngredientFormSet = generic_inlineformset_factory(Ingredient, extra=1)
StepFormSet = generic_inlineformset_factory(Step, extra=1)
# views.py
class RecipeUpdate(UpdateView):
model = Recipe
form_class = RecipeForm
template_name = 'app_recipes/recipe_add.html'
def get(self, request, *args, **kwargs):
self.object = self.get_object()
form_class = self.get_form_class()
form = self.get_form(form_class)
ingredient_form = IngredientFormSet(instance = self.object)
step_form = StepFormSet(instance = self.object)
return self.render_to_response(self.get_context_data(
form=form,
ingredient_form=ingredient_form,
step_form=step_form))
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form_class = self.get_form_class()
form = self.get_form(form_class)
ingredient_form = IngredientFormSet(self.request.POST, instance=self.object)
step_form = StepFormSet(self.request.POST, instance=self.object)
if (form.is_valid() and ingredient_form.is_valid() and step_form.is_valid()):
return self.form_valid(form, ingredient_form, step_form)
else:
return self.form_invalid(form, ingredient_form, step_form)
def form_valid(self, form, ingredient_form, step_form):
self.object = form.save()
ingredient_form.instance = self.object
ingredient_form.save()
step_form.instance = self.object
step_form.save()
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form, ingredient_form, step_form):
return self.render_to_response(self.get_context_data(
form=form,
ingredient_form=ingredient_form,
step_form=step_form))
def get_success_url(self):
return reverse('recipe_detail', kwargs={'pk': self.object.pk})
I am using the same template used by CreateView :
...
<script src="{{ STATIC_URL }}js/jquery.formset.js"></script> # django-dynamic-formset
<script type="text/javascript">
$(function() {
$(".inline.{{ ingredient_form.prefix }}").formset({
prefix: "{{ ingredient_form.prefix }}",
})
$(".inline.{{ step_form.prefix }}").formset({
prefix: "{{ step_form.prefix }}",
})
})
</script>
...
<form action="." method="post">
{% csrf_token %}
<div>
{{ form.as_p }}
</div>
<fieldset>
<legend>Recipe Ingredients</legend>
{{ ingredient_form.management_form }}
{{ ingredient_form.non_form_errors }}
{% for form in ingredient_form %}
{{ form.id }}
<div class="inline {{ ingredient_form.prefix }}">
{% for field in form.visible_fields %}
<div>
{{ field.errors }}
{{ field.label_tag }}
{{ field }}
</div>
{% endfor %}
</div>
{% endfor %}
</fieldset>
<fieldset>
<legend>Recipe Steps</legend>
{{ step_form.management_form }}
{{ step_form.non_form_errors }}
{% for form in step_form %}
{{ form.id }}
<div class="inline {{ step_form.prefix }}">
{% for field in form.visible_fields %}
<div>
{{ field.errors }}
{{ field.label_tag }}
{{ field }}
</div>
{% endfor %}
</div>
{% endfor %}
</fieldset>
<input type="submit" value="Add recipe" class="submit" />
</form>
...
NB : I use django-dynamic-formset in the template to dynamically add and remove forms in formsets.
Thank you
Related
Model
class Timetable(models.Model):
day = models.CharField(max_length=9,choices=timetable_choices)
start = models.IntegerField()
end = models.IntegerField()
period = models.CharField(max_length=12)
Views
class Timetableadding(CreateView):
model = Timetable
fields = ['day','period','start' ,'end']
success_url = '/dashboard'
What I need is to process a view similar to following image ,
NB: I am not good in js so i want a solution without the use of JS
Views
class Timetableadding(CreateView):
model = Timetable
success_url = '/dashboard/'
form_class = Timetableform
template_name = 'form.html'
def get_context_data(self, **kwargs):
context = super(Timetableadding, self).get_context_data(**kwargs)
context['formset'] = TimetableFormSet(queryset=Timetable.objects.none())
context['day_form'] = DayForm()
return context
def post(self, request, *args, **kwargs):
formset = TimetableFormSet(request.POST)
day_form = DayForm(data=request.POST)
if formset.is_valid() and day_form.is_valid():
return self.form_valid(formset,day_form)
def form_valid(self, formset,day_form):
day = day_form.cleaned_data['day']
instances = formset.save(commit=False)
for instance in instances:
instance.day = day
instance.save()
return HttpResponseRedirect('/dashboard/')
Forms
class DayForm(Form):
day = ModelChoiceField(queryset=Day.objects.all())
class Timetableform(ModelForm):
class Meta:
model = Timetable
fields = ( 'day','start', 'end', 'period')
TimetableFormSet = modelformset_factory(Timetable, fields=('start', 'end', 'period'),extra=8,)
Template
{% csrf_token %}
{{ day_form }} <br>
{{ formset.management_form }}
{% for form in formset %}
{{ form }}<br><br>
{% endfor %}
CREATE FORMS.PY
class MyForm(ModelForm):
class Meta:
model = Timetable
fields = ['day','start','end','period',]
Views.py
from django.forms import formset_factory
class YourView(CreateView):
form = formset_factory(MyForm)
model = Timetable
success_url ="Your success url"
template_name = "your template"
In Your Templates
<form method="post">{% csrf_token %}
<fieldset>
<div class="row">
<div class="form-group col-lg-12">
{{ form.management_form }}
{% for contact in form %}
<div class="link-formset">
{{ contact.as_p }}
</div>
{% endfor %}
<button class="user-sent" type="submit" value="Send"> Send</button>
</div>
</div>
</fieldset>
<script src="{% static 'forms/jquery.formset.js' %}"></script>
<script>
$('.link-formset').formset({
addText: '<i class="fa fa-plus"></i> Add User',
deleteText: '<i class="fa fa-trash-o"></i>Remove'
});
</script>
</form>
I am building a frontend form that allows someone to post an article without accessing the admin.
When the user is logged in, I would like for him/her to be able to write an article. Upon saving, I would like that user to automatically be set as the author of the article.
I am at an impasse. Any help would be much appreciated.
models.py
from django.db import models
from django.urls import reverse
from django.contrib.auth.models import User
from django.utils import timezone
class Article(models.Model):
author = models.ForeignKey(User)
title = models.CharField(max_length=65)
text = HTMLField()
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)
def publish(self):
self.published_date = timezone.now()
self.save()
def __str__(self):
return self.title
class ArticleImage(models.Model):
image = CloudinaryField('image')
image_name = models.CharField(max_length=55,
default='')
article = models.ForeignKey(Article)
def __str__(self):
return self.image_name
class ArticleTag(models.Model):
slug = models.SlugField(max_length=50,
unique=True)
article = models.ForeignKey(Article)
def __str__(self):
return self.slug
class ArticleCategory(models.Model):
slug = models.SlugField(max_length=20,
unique=True)
article = models.ForeignKey(Article)
def __str__(self):
return self.slug
forms.py
class ArticleCreationForm(ModelForm):
class Meta:
model = Article
fields = ['title', 'text']
widgets = {
'title': forms.TextInput(attrs={'placeholder': 'Please add a title. Max: 65 characters'}),
'text': forms.Textarea(attrs={'cols': 80, 'rows': 40, 'placeholder': 'Starting typing your article...'})
}
ArticleImageFormSet = inlineformset_factory(Article, ArticleImage,
fields=('image', 'image_name',),
extra=1,
max_num=1,
widgets={'image_name':
forms.TextInput(attrs={'placeholder': 'Image name'})})
ArticleTagFormSet = inlineformset_factory(Article, ArticleTag,
fields=('slug',),
extra=1,
max_num=1)
ArticleCategoryFormSet = inlineformset_factory(Article, ArticleCategory,
fields=('slug',),
extra=1,
max_num=1)
views.py
class CreateArticle(CreateView):
model = Article
form_class = ArticleCreationForm
template_name_suffix = '_add_form'
def get_success_url(self):
return reverse('accounts:detail', kwargs={'pk': self.object.pk})
def get(self, request, *args, **kwargs):
"""
Handles GET requests and instantiates blank versions of the form
and its inline formsets.
"""
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
articleimage_form = ArticleImageFormSet()
articletag_form = ArticleTagFormSet()
articlecategory_form = ArticleCategoryFormSet()
return self.render_to_response(
self.get_context_data(form=form,
articleimage_form=articleimage_form,
articletag_form=articletag_form,
articlecategory_form=articlecategory_form))
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance and its inline
formsets with the passed POST variables and then checking them for
validity.
"""
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
articleimage_form = ArticleImageFormSet(self.request.POST)
articletag_form = ArticleTagFormSet(self.request.POST)
articlecategory_form = ArticleCategoryFormSet(self.request.POST)
if (form.is_valid() and articleimage_form.is_valid() and
articletag_form.is_valid() and articlecategory_form.is_valid()):
return self.form_valid(form, articleimage_form, articletag_form,
articlecategory_form)
else:
return self.form_invalid(form, articleimage_form, articletag_form,
articlecategory_form)
def form_valid(self, form, articleimage_form, articletag_form,
articlecategory_form):
"""
Called if all forms are valid. Creates a Recipe instance along with
associated Ingredients and Instructions and then redirects to a
success page.
"""
self.object = form.save()
obj.author = request.user.username
articleimage_form.instance = self.object
articleimage_form.save()
articletag_form.instance = self.object
articletag_form.save()
articlecategory_form.instance = self.object
articlecategory_form.save()
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form, articleimage_form, articletag_form,
articlecategory_form):
"""
Called if a form is invalid. Re-renders the context data with the
data-filled forms and errors.
"""
return self.render_to_response(
self.get_context_data(form=form,
articleimage_form=articleimage_form,
articletag_form=articletag_form,
articlecategory_form=articlecategory_form))
template.html
<form enctype="multipart/form-data" action="" method="post">
{% csrf_token %}
<div class="row">
<div class="medium-9 columns">
{{ form.non_field_errors }}
<h2 class="article-identifier">Add a new article</h2>
<div class="fieldWrapper">
{{ form.title.errors }}
{{ form.title }}
<div id="title_feedback" class="text-right"></div>
</div>
<div class="fieldWrapper">
{{ form.text.errors }}
{{ form.text }}
</div>
</div>
<div class="medium-3 columns">
<div class="button-wrapper">
<input class="button" type="submit" value="Publish">
</div>
<fieldset class="image_upload">
<h2>Add an Image</h2>
{{ articleimage_form.management_form }}
{{ articleimage_form.non_form_errors }}
{% for form in articleimage_form %}
{{ form.id }}
<div class="inline {{ articleimage_form.prefix }}">
{{ form.image.errors }}
{{ form.image.label_tag }}
{{ form.image }}
{{ form.image_name.errors }}
{{ form.image_name.label_tag }}
{{ form.image_name }}
</div>
{% endfor %}
</fieldset>
<fieldset>
<h2>Add a Category</h2>
{{ articlecategory_form.management_form }}
{{ articlecategory_form.non_form_errors }}
{% for form in articlecategory_form %}
{{ form.id }}
<div class="inline {{ articlecategory_form.prefix }}">
{{ form.slug.errors }}
{{ form.slug.label_tag }}
{{ form.slug }}
</div>
{% endfor %}
</fieldset>
<hr />
<fieldset>
<h2>Add a Tag</h2>
{{ articletag_form.management_form }}
{{ articletag_form.non_form_errors }}
{% for form in articletag_form %}
{{ form.id }}
<div class="inline {{ articletag_form.prefix }}">
{{ form.slug.errors }}
{{ form.slug.label_tag }}
{{ form.slug }}
</div>
{% endfor %}
</fieldset>
</div>
</div>
</form>
Save the form with commit=False, set the user on the object, then save the object. Inside the form_valid method, you can access the user with self.request.user. You should assign the user instance, not the username as your code currently does.
obj = form.save(commit=False)
obj.author = self.request.user
...
obj.save
You should also restrict the view to logged in users. You can use the LoginRequiredMixin for this.
from django.contrib.auth.mixins import LoginRequiredMixin
class CreateArticle(LoginRequiredMixin, CreateView):
As per Django's documentation, you can just set form.instance.author to the current user (self.request.user), in the overriden form_valid method (you seem to have done something similar in your code already using other objects.). Then you can just return super().form_valid(form).
https://docs.djangoproject.com/en/2.0/topics/class-based-views/generic-editing/#models-and-request-user
In your case you seem to have a need to do other things in your form_valid method, so it may not necessarily be correct for you to return super().form_valid(form).
So, everytime I submit my form, I always get this error
MultiValueDictKeyError at /template/ 'form-21-id'
Here is my views.py:
class SomeTemplate(TemplateView):
template_name = 'template/index.html'
def get_context_data(self, **kwargs):
context = super(AttendanceTemplate, self).get_context_data(**kwargs)
instruction = Instruction(self.request.user.username)
sections_list = self.request.GET.getlist('sections_list')
term = self.request.GET.get('term', instruction.term)
enrollments = Enrollment.objects.using('api').prefetch_related('profile').filter(section_id__in=['111111'], term=term)
attendanceQuery = Enrollment.objects.using('api').prefetch_related('student').filter(section_id__in=['111111'], term=term)
#logger.error(dir(attendanceQuery))
#logger.error(attendanceQuery.values())
for enrollment in attendanceQuery:
#logger.error(enrollment.student.first_name)
attendance, created = Attendance.objects.update_or_create(
section_id=enrollment.section_id,
term=enrollment.term,
first_name=enrollment.student.first_name,
last_name=enrollment.student.last_name,
email_address=enrollment.student.email_address,
meeting_date=timezone.now(),
section_info=3,
)
something = Attendance.objects.filter(section_id__in=['111111'], term=term)
formset = AttendanceFormSet(queryset=something)
combined = zip(enrollments, formset)
context['combined'] = combined
context['formset'] = formset
return context
def post(self, request):
formset = AttendanceFormSet(request.POST)
logger.error(formset.errors)
if formset.is_valid():
formset = formset.save();
return render_to_response("template/index.html", {'formset': formset},context_instance=RequestContext(request))
else:
return HttpResponse(request.POST)
Here is my index.html file:
<form method="POST" action="">
{% csrf_token %}
{{ formset.management_form }}
{% for enrollment, form in combined %}
{{ form.id }}
{% for hidden in combined %}
{{ hidden.hidden_fields }}
{% endfor %}
<div class="wrapper-formset">
<div>
{{ form.first_name.value }}
{{ form.last_name.value }}
{{ form.email_address.value }}
</div>
<div class="clear-all"></div>
</div>
{% endfor %}
<button type="submit" class="save btn btn-default">Save</button>
</form>
I've included the following key form properties in my template as well:
form.id
management form
hidden fields
What am I missing? Doing it wrong? DB issue?
I can not get my form data to commit to my sqlite3 database. I don't see any errors. I can commit data through admin, but not through my own controller using form. I've tried many diff. combos and still no success. I would like to use class based view, please. Everything works, the form just won't save the data to database. There are no errors.
url: url(r'^create/$', CreateRequest.as_view())
forms.py:
class CreateForm(ModelForm):
date_due = forms.DateTimeField(widget=widgets.AdminSplitDateTime)
class Meta:
model = Request
fields = ['region', 'user_assigned', 'user_requester', 'description']
views.py:
class CreateRequest(LoginRequiredMixin, CreateView):
model = Request
fields = ['region', 'user_assigned', 'user_requester', 'date_due', 'description']
template_name = "requests_app/createRequest.html"
form_class = CreateForm
success_url = '/'
def form_valid(self, form):
objects = form.save()
return super(CreateRequest, self).form_valid(form)
models.py:
class Request(models.Model):
region = models.ForeignKey(Region)
completed = models.BooleanField(default=False)
user_assigned = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, related_name='user_assigned')
user_requester = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='user_requester')
date_due = models.DateTimeField()
date_completed = models.DateTimeField(null=True, blank=True)
description = models.CharField(max_length=500)
objects = models.Manager()
open_requests = OpenRequests()
completed_requests = CompletedRequests()
def mark_completed(self):
if not self.completed:
self.completed = True
self.date_completed = datetime.datetime.now()
index.html:
<h1>hi</h1>
<form action="/create/" method="post">
{% csrf_token %}
{{ form.non_field_errors }}
<div class="fieldWrapper">
{{ form.region.errors }}
<label for="id_region">Region</label>
{{ form.region }}
</div>
<div class="fieldWrapper">
{{ form.user_assigned.errors }}
<label for="id_user_assigned">User Assigned</label>
{{ form.user_assigned }}
</div>
<div class="fieldWrapper">
{{ form.user_requester.errors }}
<label for="id_user_requester">user_requester: </label>
{{ form.user_requester }}
</div>
<div class="fieldWrapper">
<p> {{ form.date_due.errors.as_text }} </p>
<label for="id_date_due">Due Date</label>
{{ form.date_due }}
</div>
<div class="fieldWrapper">
{{ form.description.errors }}
<label for="id_description">Descr.</label>
{{ form.description }}
</div>
<p><input type="submit" value="Submit Request" /></p>
{% if form.non_field_errors %}
{% for err in form%}
<div class="fieldWrapper">
<p class="form-error">{{ err }}</p>
<p class="form-error">{{ err.label_tag }} {{ field }}</p>
</div>
{% endfor %}
{% endif %}
</form>
{% endblock %}
in views.py you don't need this line: objects = form.save()
It can be
class ContaktCreateView(CreateView):
model = Contakt
form_class = ContaktForm
template_name = "www/www_contakt.html"
success_url = '/thanks/'
def form_valid(self, form):
return super(ContaktCreateView, self).form_valid(form)
Also I'm not using action in form action="/create/" method="post"
You are calling this html form via your line in urls.py:
url(r'^create/$', CreateRequest.as_view())
which is using your CreateRequest view which is using your index.html form file.
I have 2 models, Father and Son.
I have a page to register Father. On the same page I have a formset to register Son.
On page has a button "more" to add another Father and their respective Son on the same page.
Does anyone have any examples using CreateView?
Class based views are still new, so I'll write this out. The process is simple:
First, create the forms for your objects. One of the forms will be repeated. Nothing special to be done here.
class SonInline(ModelForm):
model = Son
class FatherForm(ModelForm):
model = Father
Then, create your formset:
FatherInlineFormSet = inlineformset_factory(Father,
Son,
form=SonInline,
extra=1,
can_delete=False,
can_order=False
)
Now, to integrate it with your CreateView:
class CreateFatherView(CreateView):
template_name = 'father_create.html'
model = Father
form_class = FatherForm # the parent object's form
# On successful form submission
def get_success_url(self):
return reverse('father-created')
# Validate forms
def form_valid(self, form):
ctx = self.get_context_data()
inlines = ctx['inlines']
if inlines.is_valid() and form.is_valid():
self.object = form.save() # saves Father and Children
return redirect(self.get_success_url())
else:
return self.render_to_response(self.get_context_data(form=form))
def form_invalid(self, form):
return self.render_to_response(self.get_context_data(form=form))
# We populate the context with the forms. Here I'm sending
# the inline forms in `inlines`
def get_context_data(self, **kwargs):
ctx = super(CreateFatherView, self).get_context_data(**kwargs)
if self.request.POST:
ctx['form'] = FatherForm(self.request.POST)
ctx['inlines'] = FatherInlineFormSet(self.request.POST)
else:
ctx['form'] = Father()
ctx['inlines'] = FatherInlineFormSet()
return ctx
Finally, here is the template:
The key part is the jquery django-dynamic-formset plugin that keeps adding new inline forms:
<form id="father-form" method="POST" enctype="multipart/form-data" action=".">
{% csrf_token %}
<div class="row">
{% for f in form %}
<div class="span3">{{ f.label }}<br />{{ f }}
{% if f.errors %}
{% for v in f.errors %}
<br /><span style="color:red;">{{ v }}</span>
{% endfor %}
{% endif %}
</div>
{% endfor %}
</div>
<hr />
<h2>Sons:</h2>
<table class="table-striped">
<table>
{% for f2 in inlines %}
<tr id="{{ f2.prefix }}-row">
{% for i in f2 %}
<td>
{{ i }}{% if i.errors %}<span style="color:red;">{{ i.errors }}</span>{% endif %}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
{{ inlines.management_form }}
<input type="submit" class="btn btn-primary" value="Go Go Gadget →">
</form>
<script type="text/javascript">
$(function() {
$('#father-form tr').formset({
prefix: '{{ inlines.prefix }}'
});
})
</script>