I have a model formset that I want to display 10 forms at a time using Django's Paginator, but it can't be done like paginator = Paginator(formset, 10). What's the correct way to do this, if there is a way?
This is a generic example of the solution I found to my problem:
In the forms.py file:
class MyForm(ModelForm):
class Meta:
model = MyModel
fields = ('description',)
In the views.py file:
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
FormSet = modelformset_factory(MyModel, form=MyForm, extra=0)
if request.method == 'POST':
formset = FormSet(request.POST, request.FILES)
# Your validation and rest of the 'POST' code
else:
query = MyModel.objects.filter(condition)
paginator = Paginator(query, 10) # Show 10 forms per page
page = request.GET.get('page')
try:
objects = paginator.page(page)
except PageNotAnInteger:
objects = paginator.page(1)
except EmptyPage:
objects = paginator.page(paginator.num_pages)
page_query = query.filter(id__in=[object.id for object in objects])
formset = FormSet(queryset=page_query)
context = {'objects': objects, 'formset': formset}
return render_to_response('template.html', context,
context_instance=RequestContext(request))
You need to create the formset with the objects in the present page, otherwise, when you try to do formset = FormSet(request.POST, request.FILES) in the POST method, Django raises a MultiValueDictKeyError error.
In the template.html file:
{% if objects %}
<form action="" method="post">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset.forms %}
{{ form.id }}
<!-- Display each form -->
{{ form.as_p }}
{% endfor %}
<input type="submit" value="Save" />
</form>
<div class="pagination">
<span class="step-links">
{% if objects.has_previous %}
Previous
{% endif %}
<span class="current">
Page {{ objects.number }} of {{ objects.paginator.num_pages }}
</span>
{% if objects.has_next %}
next
{% endif %}
</span>
</div>
{% else %}
<p>There are no objects.</p>
{% endif %}
A more elegant solution is to set ordered=True on the Page object so that it can be passed to a ModelFormSet.
Here is an example:
forms_per_page = 10
current_page = 1
ModelFormSet = modelformset_factory(MyModel, form=MyForm)
queryset = MyModel.objects.all()
paginator = Paginator(queryset, forms_per_page)
page_object = paginator.page(current_page)
page_object.ordered = True
form = ModelFormSet(queryset=page_object)
This is more efficient than the accepted answer because avoids the second database query that takes place in the line:
page_query = query.filter(id__in=[object.id for object in objects])
More correct way to use this
...
formset = FormSet(queryset=page_query.object_list)
...
The problem here is that you're using brands (a Page) in a context that's expecting a QuerySet. So, we need that damn QuerySet. You are in right way, but a lot of code.
In source code we have:
class Page(collections.Sequence):
def __init__(self, object_list, number, paginator):
self.object_list = object_list
self.number = number
self.paginator = paginator
...
So, our queryset in self.object_list attribute and just use it!
formset = SomeModelFormSet(queryset=objects.object_list)
Agree with Elrond Supports Monica. Fake attribute is interesting way to resolve the ordering error (Cannot reorder a query once a slice has been taken.)
But it can be fixed in one line also
queryset = queryset.order_by(Entry._meta.pk.name)
This fake ordering is need for avoid error in django.form.modelsBaseModelFormSet(BaseFormSet).get_queryset(): line #640
that make artificial ordering by pk but it impossible after slicing
(LIMIT-ations in SQL )
More detailed example
queryset = Entry.objects.all()
queryset = queryset.order_by(Entry._meta.pk.name)
paginator = Paginator(object_list=queryset, per_page=10)
page_obj = paginator.get_page(request.GET.get('page'))
EntryFormSet = modelformset_factory(Entry, EntryForm, extra=0)
entryformset = EntryFormSet(queryset=page_obj.object_list)
Related
I am looking to modify a form from the user and return that different form in django, however I have tried many different ways, all in views.py, including:
Directly modifying it by doing str(form) += "modification"
returning a new form by newform = str(form) + "modification"
creating a different Post in models, but then I realized that wouldn't work because I only want one post
All the above have generated errors such as SyntaxError: can't assign to function call, TypeError: join() argument must be str or bytes, not 'HttpResponseRedirect', AttributeError: 'str' object has no attribute 'save', and another authority error that said I can't modify a form or something like that.
Here is a snippet from views.py:
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['content']
title = ['title'] #
template_name = 'blog/post_new.html'
success_url = '/'
def form_valid(self, form):
#debugging
tempvar = (str(form).split('required id="id_content">'))[1].split('</textarea></td>')[0] #url
r = requests.get(tempvar)
tree = fromstring(r.content)
title = tree.findtext('.//title')
print(title)
form.instance.author = self.request.user
if "http://" in str(form).lower() or "https://" in str(form).lower():
if tempvar.endswith(' '):
return super().form_valid(form)
elif " http" in tempvar:
return super().form_valid(form)
elif ' ' not in tempvar:
return super().form_valid(form)
else:
return None
models.py:
class Post(models.Model):
content = models.TextField(max_length=1000)
title = models.TextField(max_length=500, default='SOME STRING') #
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
likes= models.IntegerField(default=0)
dislikes= models.IntegerField(default=0)
def __str__(self):
return (self.content[:5], self.title[:5]) #
#property
def number_of_comments(self):
return Comment.objects.filter(post_connected=self).count()
And in home.html, where the post (along with the title and content) is supposed to be shown:
<a
style="color: rgba(255, 255, 255, 0.5) !important;"
href="{% url 'post-detail' post.id %}">
<p class="mb-4">
{{ post.content }}
{{ post.title }} #
</p>
</a>
The original template I'm modifying can be found here.
Thank you so much for your help, I will be very glad to take any advice!!
Ps: I'm using Python 3.7.4
Create a forms.py file inside the app you are talking about, it should look like this:
from django import forms
from . import models
class YourFormName(forms.ModelForm):
class Meta:
model = models.your_model_name
fields = ['field1', 'field2' ,...] # Here you write the fields of your model, this fields will appear on the form where user post data
Then you call that form into your views.py so Django can render it into your template, like this:
def your_view(request, *args, **kwargs):
if request.method == 'POST':
form = forms.YourFormName(request.POST, request.FILES)
if form.is_valid():
instance = form.save(commit=False)
instance.user= request.user
instance.save()
return redirect('template.html') # To redirect if the form is valid
else:
form = forms.YourFormName()
return render(request, "template.html", {'form': form}) # The template if the form is not valid
And the last thing to do is create the template.html:
{% extends 'base.html' %}
{% block content %}
<form action="{% url 'the_url_that_renders_this_template' %}" method='POST' enctype="multipart/form-data">
{% csrf_token %}
{{ form }}
<button type="submit">Submit</button>
</form>
{% endblock content %}
If you want to take the data from DB submitted in that form, you do so with a new function in views.py:
def show_items(request, *args, **kwargs):
data = YourModelName.objects.all()
context = {
"data": data
}
return render(request, "show_items.html", context)
Then in show_items.html:
{% extends 'base.html' %}
{% block content %}
{% for item in data %}
{{item.field1}}
{{item.field2}}
...
{{The items you want to show in that template}}
{% enfor %}
{% endblock content %}
That's what you wanted to do? If not, add a further explanation on what actually you want to do
Here is my views.py code
class AttackItemInline(InlineFormSetFactory):
model = AttackItem
fields = ['attack_used','attack_image']
factory_kwargs = {'extra': 1}
class AttackImageCreateView(CreateWithInlinesView):
model = AttackImage
inlines = [AttackItemInline]
template_name = 'engine/attack_image_create.html'
fields = ['title','image','source_url']
def form_valid(self, form):
f = form.save(commit=False)
f.creator = self.request.user
f.hidden_data_found = attacks.spa(f.image)
f.save()
class AttackItemCreateView(CreateView):
model = AttackItem
template_name = 'engine/attack_item_create.html'
def formset_valid(self, form):
f = form.save(commit=False)
f.creator = self.request.user
f.save()
As you can see I have 2 forms and only 1 form valid. I don't know how to deal with that. Here is my frontend code:
{% extends 'base.html' %}
{% block content %}
<div class="col-md-4 col-md-offset-4 downdown whitebox">
<form method="post" action="{% url 'attack_image_create' %}" enctype="multipart/form-data">
{% csrf_token %}
{{ form }}
{% for formset in inlines %}
{{ formset }}
{% endfor %}
<input type="submit" value="Submit" />
</form>
</div>
{% endblock %}
How should I be handling custom logic? Like saving the f.creator, saving finding and saving f.hidden_data_found, etc.
Link to package:
https://github.com/AndrewIngram/django-extra-views
Edit: error:
null value in column "creator_id" violates not-null constraint
DETAIL: Failing row contains (2, test, attack_images/oatmeal.jpg, http://test.com, null).
I have a category dropdown list in a template. I don't want to hard code it and write separate list views for each category. So is there a way to pass a value from <a href=""> to a view? I guess self.request.GET.get('category search') never works because there is no <form method="get"> tag. Therefor it always returns None due to the return queryset.none() statement.
home.html:
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
{% if all_categories %}
{% for category in all_categories %}
<a name="category search" href="{% url 'book:category_search' category.id %}"> {{ category }} </a>
{% endfor %}
{% endif %}
</div>
book.urls.py:
urlpatterns = [
.........
path('categories/<int:pk>', views.CategorySearchView.as_view(), name='category_search'),
]
book.views.py:
class CategorySearchView(generic.ListView):
template_name = 'book/search.html'
model = Book
context_object_name = 'book_list'
paginate_by = 100
def get_queryset(self):
queryset = super().get_queryset()
search = self.request.GET.get('category search')
if search:
queryset = Book.objects.filter(categories__id=search)
return queryset
else:
return queryset.none()
Since your URL has <int:pk>,
path('categories/<int:pk>', views.CategorySearchView.as_view(), name='category_search'),
you can access self.kwargs['pk'] in the view:
def get_queryset(self):
queryset = super().get_queryset()
search = self.kwargs.get('pk')
if search:
queryset = Book.objects.filter(categories__id=search)
return queryset
else:
return queryset.none()
As an aside, you would use self.request.GET if pk was in the querystring, e.g. /categories/?pk=1. When you submit a form with method=GET, the form fields will be included in the querystring. But you can also have a link with a querystring, e.g. my link.
I'm new in django.
I has a django application where stores products categorized by 'X' and 'Y'.
views.py
...
class CartListView(ListView):
template_name = 'checkout/list.html'
context_object_name = 'product_list'
def get_queryset(self):
return Product.objects.filter(category__slug='X') | Product.objects.filter(category__slug='Y')
def get_context_data(self, **kwargs):
context = super(CartListView, self).get_context_data(**kwargs)
context['minicurso'] = get_object_or_404(Category, slug='X')
context['pacotes'] = get_object_or_404(Category, slug='Y')
return context
...
In my views.py I filter this products by your categories slug.
The problem is, I'm trying to render the products in category 'X' on top the page and the products in category 'Y' down with a text between them. How I can do this?
list.html
{% for category in product_list %}
{{ category.name }}
{% endfor %}
<p>
Any text
</p>
{% for category in product_list %}
{{ category.name }}
{% endfor %}
First off, you should use IN operator over | when populating the filtered queryset:
def get_queryset(self):
return Product.objects.filter(category__slug__in=["X", "Y"])
Secondly, you can't filter queryset by any field in the template unless you write a custom template tag which does that. However, it defeats the purpose of separation of presentation code from data logic. Filtering models is data logic, and outputting HTML is presentation. Thus, you need to override get_context_data and pass each queryset into the context:
def get_context_data(self, **kwargs):
context = super(CartListView, self).get_context_data(**kwargs)
context['minicurso'] = get_object_or_404(Category, slug='X')
context['pacotes'] = get_object_or_404(Category, slug='Y')
context["x_product_list"] = self.get_queryset().filter(category=context['minicurso'])
context["y_product_list"] = self.get_queryset().filter(category=context['pacotes'])
return context
Then you can use them in the template:
{% for category in x_product_list %}
{{ category.name }}
{% endfor %}
...
{% for category in y_product_list %}
{{ category.name }}
{% endfor %}
I'm not sure how to filter dropdown based on user id.
Not I want for user id 2.
I want exactly like this for user id 2.
Model
#python_2_unicode_compatible # only if you need to support Python 2
class PredefinedMessage(models.Model):
user = models.ForeignKey(User)
list_name = models.CharField(max_length=50)
list_description = models.CharField(max_length=50)
def __str__(self):
return self.list_name
class PredefinedMessageDetail(models.Model):
predefined_message_detail = models.ForeignKey(PredefinedMessage)
message = models.CharField(max_length=5000)
View
class PredefinedMessageDetailForm(ModelForm):
class Meta:
model = PredefinedMessageDetail
fields = ['predefined_message_detail', 'message']
exclude = ('user',)
def predefined_message_detail_update(request, pk, template_name='predefined-message/predefined_message_detail_form.html'):
if not request.user.is_authenticated():
return redirect('home')
predefined_message_detail = get_object_or_404(PredefinedMessageDetail, pk=pk)
form = PredefinedMessageDetailForm(request.POST or None, instance=predefined_message_detail)
if form.is_valid():
form.save()
return redirect('predefined_message_list')
return render(request, template_name, {'form':form})
html file
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<form method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>
{% endblock %}
You can do it in view itself using
form = PredefinedMessageDetailForm(request.POST or None, instance=predefined_message_detail)
form.fields["predefined_message_detail"].queryset= PredefinedMessage.objects.filter(user=request.user)
But filtering happens based on request.user so it should be logged in.Consider that also. Hope this helps