Using formset with ContentType - python

I'm having trouble using the UpdateView for a view consisting of a form and formset.
I have the following models: Item and Picture.
Picture is defined as:
class Picture(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255, blank=False)
content_type = models.ForeignKey(ContentType, verbose_name="content type",
related_name="content_type_set_for_%(class)s")
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey("content_type", "object_id")
I have several models that contain pictures. For example, in the Item model:
class Item(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255, blank=False)
pictures = generic.GenericRelation(Picture)
I have the following ItemCreateForm:
class ItemCreateForm(ModelForm):
def __init__(self, *args, **kwargs):
super(ItemCreateForm, self).__init__(*args, **kwargs)
class Meta:
model = Item
The PictureForm:
class PictureForm(forms.ModelForm):
id = forms.IntegerField(widget=forms.HiddenInput)
def __init__(self, *args, **kwargs):
super(PictureForm, self).__init__(*args, **kwargs)
def save(self):
data = self.cleaned_data
obj = Picture(**data);
# do something to obj
# obj.save()
class Meta:
model = Picture
fields = ['id', 'name']
And the view:
class ItemUpdateView(UpdateView):
form_class = ItemCreateForm
template_name = 'item/new.html'
model = Item
success_url = '/items/'
def get_context_data(self, **kwargs):
context = super(ItemUpdateView, self).get_context_data(**kwargs)
item = context['object']
# Dont' create any extra forms when showing an update view
PictureFormSet = formset_factory(PictureForm, extra=0)
return {'form': kwargs['form'],
'picture_formset': UploadFormSet(initial = [ model_to_dict(a) for pic in item.pictures.all()])}
def post(self, request, *args, **kwargs):
self.object = self.get_object()
item_form = ItemCreateForm(request.POST, instance=self.object)
if item_form.is_valid():
item = item_form.save(commit=False)
item.save()
# How do update the pictures?
This is my urls.py:
url(r'^items/(?P<pk>\d+)/update/$', ItemUpdateView.as_view(), name='item_update')
The template:
<form action="" method="post" enctype="multipart/form-data">
{% for field in form %}
# do something
{% endfor %}
{{ picture_formset.management_form }}
{% for form in picture_formset.forms %}
# do something
{% endfor %}
<input name="commit" type="submit" value="Submit" />
</form>
I'm new to Django.
The user can dynamically(via jQuery) add/remove pictures through the Picture form in the single template that is used to display the item and multiple pictures.
1 I had to include the id as a hidden field for the picture, otherwise the pictures will be inserted instead of an Update. QN: Is there a better way to do this?
2 How do I update the picture model? Currently request.POST doesn't have all the fields in the model, thus the model is complaining of NULL fields? I'm totally at lost how to deal with formset in an UpdateView and is not the main form, like a simple example of UpdateView with the pk in the url.
PictureFormSet = formset_factory(PictureForm)
picture_formset = PictureFormSet(request.POST, request.FILES)
for picture_form in picture_formset.forms:
picture_form.save()

Related

How to add custom method in django forms

I am new to django, and I am creating a vacation application. I want to be able to when I create a new trip, the user that created the trip becomes a member of that trip.
here is my models.py file:
class Trip(models.Model):
trip_name = models.CharField(max_length=255,unique=False)
start_date = models.DateField(default=datetime.date.today)
end_date = models.DateField(default=datetime.date.today)
slug = models.SlugField(allow_unicode=True,unique=True)
members = models.ManyToManyField(User,through='TripMember')
def __str__(self):
return self.trip_name
def save(self,*args,**kwargs):
self.slug = slugify(self.trip_name)
super().save(*args,**kwargs)
def get_absolute_url(self):
return reverse('trips:single',kwargs={'slug':self.slug})
class Meta:
ordering = ['start_date']
class TripMember(models.Model):
trip = models.ForeignKey(Trip,null=True,related_name='memberships',on_delete=models.SET_NULL)
user = models.ForeignKey(User,null=True,related_name='user_trips',on_delete=models.SET_NULL)
def __str__(self):
return self.user.username
class Meta:
unique_together = ('trip','user')
this is my forms.py file:
class TripCreateForm(forms.ModelForm):
class Meta:
fields = ('trip_name','start_date','end_date')
model = Trip
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["trip_name"].label = "Trip Name"
self.fields["start_date"].label = "Start Date"
self.fields["end_date"].label = "End Date"
here is my views.py file:
class CreateTrip(CreateView):
form_class = TripCreateForm
template_name = 'trips/trip_form.html'
and my trip_form.html page:
<form action="{% url 'trips:create' %}" method="post" id='tripForm'>
{% csrf_token %}
{% bootstrap_form form %}
<input type="submit" class="btn btn-primary btn-large" value="Create">
</form>
Where would I put the code to set the user as a tripmember and why?Also, if there is a better way to have set this up please let me know! I was gonna put it in the save part of the model but I am not quite sure if that is correct. Thanks!
You can override the form_valid() method of the CreateTrip class in your view:
def form_valid(self, form):
"""If the form is valid, save the associated model."""
self.object = form.save()
# add the current user to the members list of the trip
user = self.request.user
self.object.members.add(user)
return super().form_valid(form)

Django Adding User details to a UserPost List View

I have a UserPost List View which is a view for a specific user's posts. I am looping the posts of this specific user but I want to add this user's profile details like profile image and other details like email.
So, I am not sure on how to add the user details of the user's post names as designer not the logged-in user.
I can add the user/designer details in every looped post but I don't want it to be repeated with every post I just want it to appear once just like {{ view.kwargs.username }} as this page is realted only to this user/designer
Here is the models.py
class Post(models.Model):
designer = models.ForeignKey(User, on_delete=models.CASCADE, related_name="post")
title = models.CharField(max_length=100, unique=True)
here is the profile model related to every user
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
image = models.ImageField(default='default.jpg', upload_to='profile_pics')
class UserRegisterForm(UserCreationForm):
email = forms.EmailField()
first_name = forms.CharField()
last_name = forms.CharField()
class Meta:
model = User
fields = ['username', 'first_name', 'last_name',
'email']
here is the views for the userpostlist which is filtered by a specific user/designer not the logged in user
class UserPostListView(ListView):
model = Post
template_name = "user_posts.html"
context_object_name = 'posts'
queryset = Post.objects.filter(admin_approved=True)
paginate_by = 6
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Post.objects.filter(designer=user, admin_approved=True).order_by('-date_posted')
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
has_items = Item.objects.filter(designer__username=self.kwargs['username']).exists()
context['has_items'] = has_items
return context
here is the template
{% if has_items %}
<h1 class="display-4">Hello, this is {{ view.kwargs.username }} </h1>
<img class="profile_image" src={{ designer.profile.image.url }}> <----------- I want it to appear
{{ designer.email }}<----------- I want it to appear
{% else %}
Show nothing
{% endif %}
{% for post in posts %}
Post details
{% endfor %}
You can add this to the context:
from django.contrib.auth import get_user_model
class UserPostListView(ListView):
model = Post
template_name = 'user_posts.html'
context_object_name = 'posts'
queryset = Post.objects.filter(admin_approved=True)
paginate_by = 6
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(
designer__username=self.kwargs['username']
).order_by('-date_posted')
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['designer'] = designer = get_object_or_404(
get_user_model(),
username=self.kwargs['username']
)
context['has_items'] = Item.objects.filter(designer=designer).exists()
return context

ValidationError ignored in Custom Django Crispy Form

I have a rather complex Django form that affects 3 models and part of which includes an inline formset. I found a nice solution to building the form at https://dev.to/zxenia/django-inline-formsets-with-class-based-views-and-crispy-forms-14o6. I extended that solution and added a third model in a similar way that the formset was added (using a custom Django Crispy Form and inserting it using the Crispy Forms Layout features).
My problem is that any validation errors raised on either of the two inserted forms (the formset and the small subform) are simply ignored - the main form posts correctly and raised ValidationErrors are displayed in the form as errors allowing the user to correct any mistakes and its data is correctly saved to the database. If the subform and formset are valid, their data gets saved correctly as well. However, if the data in the subform and formset is not valid, the form never shows the errors to give the user a chance to correct their mistake, and the data is simply ignored and never saved to the database - the main model's data saves fine though.
My question is, how do I get the form to refresh with errors displayed in the added subform and formset allowing the user to correct their mistakes?
Most of the code below is from the quite good post referenced above with a third model added
Models:
from django.db import models
from django.contrib.auth.models import User
class Collection(models.Model):
subject = models.CharField(max_length=300, blank=True)
owner = models.CharField(max_length=300, blank=True)
note = models.TextField(blank=True)
created_by = models.ForeignKey(User,
related_name="collections", blank=True, null=True,
on_delete=models.SET_NULL)
def __str__(self):
return str(self.id)
class CollectionTitle(models.Model):
"""
A Class for Collection titles.
"""
collection = models.ForeignKey(Collection,
related_name="has_titles", on_delete=models.CASCADE)
name = models.CharField(max_length=500, verbose_name="Title")
language = models.CharField(max_length=3)
Class CollectionTxn(models.Model):
"""
A Class for Collection transactions.
"""
collection = models.ForeignKey(Collection,
related_name="has_txn", on_delete=models.CASCADE)
number_received= models.IntegerField()
date_received= models.DateField()
class Meta:
'''
If 2 rows are entered with the same information, a validation error is raised, but it just
doesn't save the data at all instead of refreshing the form showing the error.
'''
unique_together = ('number_received', 'date_received')
forms.py:
from django import forms
from .models import Collection, CollectionTitle
from django.forms.models import inlineformset_factory
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Field, Fieldset, Div, Row, HTML, ButtonHolder, Submit
from .custom_layout_object import Formset, Subform
import re
class CollectionTitleForm(forms.ModelForm):
class Meta:
model = CollectionTitle
exclude = ()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
formtag_prefix = re.sub('-[0-9]+$', '', kwargs.get('prefix', ''))
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
Row(
Field('name'),
Field('language'),
Field('DELETE'),
css_class='formset_row-{}'.format(formtag_prefix)
)
)
CollectionTitleFormSet = inlineformset_factory(
Collection, CollectionTitle, form=CollectionTitleForm,
fields=['name', 'language'], extra=1, can_delete=True
)
class CollectionForm(forms.ModelForm):
class Meta:
model = Collection
exclude = ['created_by', ]
def __init__(self, *args, **kwargs):
super(CollectionForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = True
self.helper.form_class = 'form-horizontal'
self.helper.label_class = 'col-md-3 create-label'
self.helper.field_class = 'col-md-9'
self.helper.layout = Layout(
Div(
Field('subject'),
Field('owner'),
Fieldset('Add titles',
Formset('titles')),
Field('note'),
Subform('transactions'),
HTML("<br>"),
ButtonHolder(Submit('submit', 'Save')),
)
)
class CollectionTxnForm(forms.ModelForm):
class Meta:
model = CollectionTxn
exclude = ()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['collection'].widget = HiddenInput()
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
Row(
Field('number_received'),
Field('date_received'),
)
)
views.py:
from .models import *
from .forms import *
from django.views.generic.edit import CreateView, UpdateView
from django.urls import reverse_lazy
from django.db import transaction
class CollectionCreate(CreateView):
model = Collection
template_name = 'mycollections/collection_create.html'
form_class = CollectionForm
success_url = None
def get_context_data(self, **kwargs):
data = super(CollectionCreate, self).get_context_data(**kwargs)
if self.request.POST:
data['titles'] = CollectionTitleFormSet(self.request.POST)
data['transactions'] = CollectionTrxForm(self.request.POST)
else:
data['titles'] = CollectionTitleFormSet()
data['transactions'] = CollectionTrxForm()
return data
def form_valid(self, form):
context = self.get_context_data()
titles = context['titles']
transactions = context['transactions']
with transaction.atomic():
form.instance.created_by = self.request.user
self.object = form.save()
if titles.is_valid():
titles.instance = self.object
titles.save()
if transactions.is_valid():
transactions.save()
return super(CollectionCreate, self).form_valid(form)
def get_success_url(self):
return reverse_lazy('mycollections:collection_detail', kwargs={'pk': self.object.pk})
custom_layout_object.py
from crispy_forms.layout import LayoutObject, TEMPLATE_PACK
from django.shortcuts import render
from django.template.loader import render_to_string
class Formset(LayoutObject):
template = "mycollections/formset.html"
def __init__(self, formset_name_in_context, template=None):
self.formset_name_in_context = formset_name_in_context
self.fields = []
if template:
self.template = template
def render(self, form, form_style, context, template_pack=TEMPLATE_PACK):
formset = context[self.formset_name_in_context]
return render_to_string(self.template, {'formset': formset})
class SubForm(LayoutObject):
template = "mycollections/subform.html"
def __init__(self, subform_name_in_context, template=None):
self.subform_name_in_context = subform_name_in_context
self.fields = []
if template:
self.template = template
def render(self, subform, form_style, context, template_pack=TEMPLATE_PACK):
subform = context[self.subform_name_in_context]
return render_to_string(self.template, {'subform': subform})
formset.html
{% load crispy_forms_tags %}
{% load staticfiles %}
<style type="text/css">
.delete-row {
align-self: center;
}
</style>
{{ formset.management_form|crispy }}
{% for form in formset.forms %}
{% for hidden in form.hidden_fields %}
{{ hidden|as_crispy_field }}
{% endfor %}
{% crispy form %}
{% endfor %}
<br>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="{% static 'mycollections/libraries/django-dynamic-formset/jquery.formset.js' %}"></script>
<script type="text/javascript">
$('.formset_row-{{ formset.prefix }}').formset({
addText: 'add another',
deleteText: 'remove',
prefix: '{{ formset.prefix }}',
});
</script>
subform.html
{% load crispy_forms_tags %}
{% crispy subform %}
collection_create.html
{% extends "mycollections/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="container">
<div class="card">
<div class="card-header">
Create collection
</div>
<div class="card-body">
{% crispy form %}
</div>
</div>
</div>
{% endblock content %}
Basically, for the fields associated with the formset and subform added to the layout, validation errors are still raised, but they do not bubble up to the form level to show the errors, they are just ignored and the data is never saved. The "main" model works fine and validationerrors are displayed for its fields. If there is no invalid data, the main form, the subform, and the formset's data are all saved correctly. If there is invalid data in the formset or subform, the user never gets a chance to correct that data.
Any help as to where I would add the code needed so that if any invalid data entered in the formset or subform would cause the form to refresh displaying errors instead of just ignoring and not saving invalid data would be appreciated.
After much debugging and analyzing code, both my own and Crispy, I have solved my own problem. It was simply a matter of checking for the subform and formset validations, and if not valid, re-rendering the form.
Here is the new form_valid() method from the view that does this:
def form_valid(self, form):
context = self.get_context_data()
titles = context['titles']
transactions = context['transactions']
with transaction.atomic():
form.instance.created_by = self.request.user
if titles.is_valid() and transactions_is_valid():
self.object = form.save() #only save form if other subforms validate
titles.instance = self.object
# Any other field processing goes here
titles.save()
transactions.save()
else:
# If any subform or subformset is invalid, re-render the form showing errors
context.update({'titles': titles})
context.update({'transactions': transactions})
return self.render_to_response(context)
return super(CollectionCreate, self).form_valid(form)
With this, any clean methods in the formset and subform or if there are any other errors (for example if no two rows of the formset can be the same because unique_together() declared in model),the form will refresh showing all errors on all three combined form/formsets allowing the user to correct those errors.
Now - to do it with Ajax so the page does not refresh :)

How can i display a Foreign Key on a select tag in my django template?

I'm trying to display some information on a SELECT tag on my template that passes through a for loop and can't seem to find a how to. The examples I find on the internet aren't dynamics (from another table) and I'm really struggling on that. Bellow are my codes, hope you guys can help me on this.
This is my models.py
class Books(models.Model):
[...]
category_cod_category = models.ForeignKey(
'Category',
on_delete = models.CASCADE
)
class Meta:
db_table = 'Books'
class Category(models.Model):
category_name = models.CharField(
max_length = 45
)
def __str__(self):
return '%s' % self.category_name
class Meta:
db_table = 'Category'
This is my views.py and this bit of code is from another template where I have a button for editing.
def edit(request,id):
book = Books.objects.get(id=id)
return render(request,'edit.html',{'edit_book':book})
This is my template edit.html where i need the category of the books to be displayed. Every other field is OK.
<form method="POST" class="post-form" action="/update/{{ book.id }}">
{% csrf_token %}
[...]
<div class="container">
<label>Categoria do Livro:</label>
<select class="selectpicker form-control" data-live-search="true">
{% for books in Books %}
<option value="{{ edit_livro.category_cod_category}}">{{ edit_livro.category_cod_category}}</option>
{% endfor %}
</select>
<div>
[...]
Can you guys help me? Or send me a place where I can learn how to solve this?
Sorry for any misspellings, I'm not a native english writer.
Thanks!
EDIT:
I'm using ModelForms for my insert template, It's listed below:
class BooksForm(forms.ModelForm):
class Meta:
model = Books
fields = [ ...,'category_cod_category', ... ]
# Step:1
# Create url for views (listing, create, update)
urls.py
path(r'book-listing/', BookListView.as_view(), name='book-info-listing'),
path(r'book-info-create/', BookCreateView.as_view(), name='create-book-info'),
path(r'book-info-edit/<int:id>', BookUpdateView.as_view(), name='edit-book-info'),
# Step:2
# Create form for your model
forms.py
# You just need to set the queryset for your field of form, if you want to get the select tag for all your category
class BooksForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.fields['category_cod_category'].queryset = Category.objects.all()
class Meta:
model = Books
fields = [ ...,'category_cod_category', ... ]
# Step:3
# Create views for your model
views.py
# For Listing a book info
class BookListView(generic.ListView):
model = Books
def get_queryset(self):
books = self.model.objects.all()
return books
# For Creating a new book
class BookCreateView(generic.CreateView):
model = Books
form_class = BookForm
template_name = 'project_app/book-form.html'
def form_valid(self, form):
form.save(self.request)
return redirect('book-info-listing')
# For Editing or updating a book
class BookUpdateView(generic.UpdateView):
model = Books
form_class = BookForm
template_name = 'project_app/book-form.html'
success_url = reverse_lazy('book-info-listing')
# get the object
def get_object(self, *args, **kwargs):
book_info = get_object_or_404(Books, pk=self.kwargs['id'])
return book_info
def form_valid(self, form):
form.save(self.request)
return redirect('book-info-listing')
# Step:4 Render form in your template
book-form.html
<form method="POST" class="books_form">
{% csrf_token %}
{{form.category_cod_category}}
</form>

How do I prepopulate a form with values from a database in Django?

I'm writing what should be a very simple todo app. The problem is that the edit view is giving me fits! I'm trying to populate a form with data from the database, and it's just not doing the right thing. I've tried the info from this page, but the translation into class-based views must have broken something, or I'm just not using the right kind of form.
Here's the code for the model:
class Todo(models.Model):
id = models.AutoField(primary_key=True)
todo = models.CharField(max_length=255, unique=True)
todo_detail = models.TextField(default='')
date_created = models.DateField(default=timezone.now())
estimated_completion = models.DateTimeField(default=timezone.now())
maybe_completed = models.BooleanField("Completed?", default=False)
def __unicode__(self):
return self.todo
The view code, the commented out bit is from the link:
class TodoEditView(FormView):
model = Todo
form_class = TodoEditForm
template_name = 'todo_edit.html'
#def get(self, request, *args, **kwargs):
# form = self.form_class()
# form.fields['todo'].queryset = Todo.objects.get(id=self.kwargs['pk'])
# form.fields['todo_detail'].queryset = Todo.objects.get(
# id=self.kwargs['pk'])
# form.fields['date_created'].queryset = Todo.objects.get(
# id=self.kwargs['pk'])
# form.fields['estimated_completion'].queryset = Todo.objects.get(
# id=self.kwargs['pk'])
# form.fields['maybe_completed'].queryset = Todo.objects.get(
# id=self.kwargs['pk'])
# template_vars = RequestContext(request, {
# 'form': form
# })
# return render_to_response(self.template_name, template_vars)
def get_context_data(self, **kwargs):
context = super(TodoEditView, self).get_context_data(**kwargs)
context['todo'] = Todo.objects.get(id=self.kwargs['pk'])
return context
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
todo = request.POST['todo']
todo_detail = request.POST['todo_detail']
estimated_completion = request.POST['estimated_completion']
date_created = request.POST['date_created']
t = Todo(todo=todo, todo_detail=todo_detail,
estimated_completion=estimated_completion,
date_created=date_created)
t.save()
return redirect('home')
The form code:
class TodoEditForm(forms.ModelForm):
class Meta:
model = Todo
exclude = ('id', )
And the template code:
{% extends 'todos.html'%}
{% block content %}
<form method="post" action="{% url 'add' %}">
<ul>
{{ form.as_ul }}
{% csrf_token %}
</ul>
{{todo.todo}}
</form>
{% endblock %}
What the heck am I doing wrong?
You should use an UpdateView, not a FormView. That will take care of prepopulating your form.
Also note you don't need any of the logic in the post method - that is all taken care of by the generic view class.

Categories

Resources