I am doing some filtering. I have initial results on my page, then there is a form, that on select, I want to filter some data and update template.
The template doesn't seem to be updated though.
from .services import service
# Home Page View
def home_view(request, *args, **kwargs):
update_list = service.get_updates()
if request.method == 'POST':
filter_string = request.POST.get('filter')
update_list = [update for update in update_list if update['type'] == filter_string]
print(len(update_list))
page = request.GET.get('page', 1)
paginator = Paginator(update_list, 2)
try:
updates = paginator.page(page)
except PageNotAnInteger:
updates = paginator.page(1)
except EmptyPage:
updates = paginator.page(paginator.num_pages)
context = {
'updates': updates
}
return render(request, "home.html", context)
Template file:
<form method="post" id="filterformhome">
{% csrf_token %}
<select class="frm-input" onchange="homeFilter(this);">
<option value="job">{% trans "Jobs" %}</option>
<option value="market">{% trans "Products" %}</option>
<option value="question">{% trans "Questions" %}</option>
<option value="blog">{% trans "Blogs" %}</option>
</select>
</form>
{% for update in updates %}
{{update.title}}
{% endfor %}
What would be the better way of doing this?
You need to use JS for that, otherwise you will only see the change reflected by refreshing the page.
Related
I have a form with MultipleChoiceField. I am able to save the data correctly. Now, if a user wants to edit that form he/she should see already selected items in the dropdown along with remaining all the other option. I want this as a function-based view. eg. I have 5 products in the dropdown and at the time of form submission, I selected products 1 and 2. Now, when I click on edit I should be able to see products 1 and 2 selected along with the other 3 products as unselected.Just help me with the edit view of this form. I am using same template for create and update. the code is messy.
models.py
class Lead(models.Model):
state = models.CharField(_("State"), max_length=255, blank=True, null=True)
type = models.CharField(max_length=20,choices=TYPE,blank=True,null=True)
products = models.ManyToManyField(Product,related_name='company_products',limit_choices_to=5,blank=True,null=True)
forms.py my customized form just sharing products section as code is getting bigger.
class LeadForm(forms.ModelForm):
product_queryset = []
products = forms.MultipleChoiceField(choices=product_queryset)
def __init__(self, *args, **kwargs):
assigned_users = kwargs.pop('assigned_to', [])
super(LeadForm, self).__init__(*args, **kwargs)
self.fields['products'].required = False
self.fields['products'].choices = [(pro.get('id'),pro.get('name')) for pro in Product.objects.all().values('id','name')]
views.py i am sharing just products section as the code is bit lenghty.
def update_lead(request, pk):
lead_record = Lead.objects.filter(pk=pk).first()
template_name = "create_lead.html"
users = []
if request.user.role == 'ADMIN' or request.user.is_superuser:
users = User.objects.filter(is_active=True).order_by('email')
else:
users = User.objects.filter(role='ADMIN').order_by('email')
status = request.GET.get('status', None)
initial = {}
if status and status == "converted":
error = "This field is required."
lead_record.status = "converted"
initial.update({
"status": status, "lead": lead_record.id})
error = ""
form=LeadForm(instance=lead_record,initial=initial,assigned_to=users)
if request.POST:
form = LeadForm(request.POST, request.FILES,
instance=lead_record,
initial=initial, assigned_to=users)
if form.is_valid():
if request.POST.getlist('products', []):
lead_obj.products.clear()
lead_obj.products.add(*request.POST.getlist('products'))
else:
lead_obj.products.clear()
status = request.GET.get('status', None)
success_url = reverse('leads:list')
if status:
success_url = reverse('accounts:list')
return JsonResponse({'error': False, 'success_url': success_url})
return JsonResponse({'error': True, 'errors': form.errors})
context = {}
context["lead_obj"] = lead_record
context["lead_form"] = form
context["teams"] = Teams.objects.all()
context['products'] = Product.objects.all()
context["assignedto_list"] = [
int(i) for i in request.POST.getlist('assigned_to', []) if i]
return render(request, template_name, context)
create_lead.html i am using this html for create as well as update view. I am just sharing products div section
<div class="form-group" style="height:20px;">
<label for="exampleInputEmail1">Product{% if products.field %}<span
class="error">*</span>{% endif %}</label>
<select multiple="multiple">
{% for product in products %}
<option value="{{product.pk}}" {% if product in products.company_products.all %} selected="" {% endif %}>{{product.name}}</option>
{% endfor %}
</select>
</div>
On template, you should to avoid render form "by hand". You should to render form using django forms rendering system.:
<form action="/your-name/" method="post">
{% csrf_token %}
{{ lead_form }}
<input type="submit" value="Submit">
</form>
If you need to render just this field manually:
<div class="form-group" style="height:20px;">
<label for="exampleInputEmail1">Product{% if products.field %}<span
class="error">*</span>{% endif %}</label>
{{ lead_form.products }}
</div>
In a Django social networking website I built, users can chat in a general room, or create private groups.
Each user has a main dashboard where all the conversations they're a part of appear together, stacked over one another (paginated by 20 objects). I call this the unseen activity page. Every unseen conversation on this page has a text box a user can directly type a reply into. Such replies are submitted via a POST request inside a <form>.
The action attribute of each <form> points to different urls, depending on which type of reply was submitted (e.g. home_comment, or group_reply). This is because they have different validation and processing requirements, etc.
The problem is this: If a ValidationError is raised (e.g. the user typed a reply with forbidden characters), it gets displayed on multiple forms in the unseen_activity page, instead of just the particular form it was generated from. How can I ensure all ValidationErrors solely appear over the form they originated from? An illustrative example would be great!
The form class attached to all this is called UnseenActivityForm, and is defined as such:
class UnseenActivityForm(forms.Form):
comment = forms.CharField(max_length=250)
group_reply = forms.CharField(max_length=500)
class Meta:
fields = ("comment", "group_reply", )
def __init__(self,*args,**kwargs):
self.request = kwargs.pop('request', None)
super(UnseenActivityForm, self).__init__(*args, **kwargs)
def clean_comment(self):
# perform some validation checks
return comment
def clean_group_reply(self):
# perform some validation checks
return group_reply
The template looks like so:
{% for unseen_obj in object_list %}
{% if unseen_obj.type == '1' %}
{% if form.comment.errors %}{{ form.comment.errors.0 }}{% endif %}
<form method="POST" action="{% url 'process_comment' pk %}">
{% csrf_token %}
{{ form.comment }}
<button type="submit">Submit</button>
</form>
{% if unseen_obj.type == '2' %}
{% if form.group_reply.errors %}{{ form.group_reply.errors.0 }}{% endif %}
<form method="POST" action="{% url 'process_group_reply' pk %}">
{% csrf_token %}
{{ form.group_reply }}
<button type="submit">Submit</button>
</form>
{% endif %}
{% endfor %}
And now for the views. I don't process everything in a single one. One function takes care of generating the content for the GET request, and others take care handling POST data processing. Here goes:
def unseen_activity(request, slug=None, *args, **kwargs):
form = UnseenActivityForm()
notifications = retrieve_unseen_notifications(request.user.id)
page_num = request.GET.get('page', '1')
page_obj = get_page_obj(page_num, notifications, ITEMS_PER_PAGE)
if page_obj.object_list:
oblist = retrieve_unseen_activity(page_obj.object_list)
else:
oblist = []
context = {'object_list': oblist, 'form':form, 'page':page_obj,'nickname':request.user.username}
return render(request, 'user_unseen_activity.html', context)
def unseen_reply(request, pk=None, *args, **kwargs):
if request.method == 'POST':
form = UnseenActivityForm(request.POST,request=request)
if form.is_valid():
# process cleaned data
else:
notifications = retrieve_unseen_notifications(request.user.id)
page_num = request.GET.get('page', '1')
page_obj = get_page_obj(page_num, notifications, ITEMS_PER_PAGE)
if page_obj.object_list:
oblist = retrieve_unseen_activity(page_obj.object_list)
else:
oblist = []
context = {'object_list': oblist, 'form':form, 'page':page_obj,'nickname':request.user.username}
return render(request, 'user_unseen_activity.html', context)
def unseen_group_reply(group_reply, pk=None, *args, **kwargs):
#similar processing as unseen_reply
Note: the code is a simplified version of my actual code. Ask for more details in case you need them.
Following the discussion in the comments above:
What I suggest is that you create a form for each instance in the view. I have refactored your code to have a function which returns object lists which you can use in both unseen_reply and group_reply functions:
def get_object_list_and_forms(request):
notifications = retrieve_unseen_notifications(request.user.id)
page_num = request.GET.get('page', '1')
page_obj = get_page_obj(page_num, notifications, ITEMS_PER_PAGE)
if page_obj.object_list:
oblist = retrieve_unseen_activity(page_obj.object_list)
else:
oblist = []
# here create a forms dict which holds form for each object
forms = {}
for obj in oblist:
forms[obj.pk] = UnseenActivityForm()
return page_obj, oblist, forms
def unseen_activity(request, slug=None, *args, **kwargs):
page_obj, oblist, forms = get_object_list_and_forms(request)
context = {
'object_list': oblist,
'forms':forms,
'page':page_obj,
'nickname':request.user.username
}
return render(request, 'user_unseen_activity.html', context)
Now, you need to access the form in template using the object id from forms dict.
{% for unseen_obj in object_list %}
<!-- use the template tag in the linked post to get the form using obj pk -->
{% with forms|get_item:unseen_obj.pk as form %}
{% if unseen_obj.type == '1' %}
{% if form.comment.errors %}{{ form.comment.errors.0 }}{% endif %}
<form method="POST" action="{% url 'process_comment' pk %}">
{% csrf_token %}
{{ form.comment }}
<button type="submit">Submit</button>
</form>
{% elif unseen_obj.type == '2' %}
{% if form.group_reply.errors %}{{ form.group_reply.errors.0 }}{% endif %}
<form method="POST" action="{% url 'process_group_reply' pk %}">
{% csrf_token %}
{{ form.group_reply }}
<button type="submit">Submit</button>
</form>
{% endif %}
{% endwith %}
{% endfor %}
While processing the reply, you again need to attach the form which throws error with the particular object pk:
def unseen_reply(request, pk=None, *args, **kwargs):
if request.method == 'POST':
form = UnseenActivityForm(request.POST,request=request)
if form.is_valid():
# process cleaned data
else:
page_obj, oblist, forms = get_object_list_and_forms(request)
# explicitly set the form which threw error for this pk
forms[pk] = form
context = {
'object_list': oblist,
'forms':forms,
'page':page_obj,
'nickname':request.user.username
}
return render(request, 'user_unseen_activity.html', context)
I'm fairly new to Wagtail, and I am in the process of creating a site that will have a Resources (blog) section and I'm not sure how to implement pagination so that there are only 5 posts on each page and the user has to click a number (1, 2, 3, etc.) to go to the next page to see the next 5 posts.
I have this in my template for the pagination section of the resource/blog index page:
<ul class="pagination">
<li><i class="fa fa-angle-left"></i></li>
<li class="active">1</li>
<li>2</li>
<li>3</li>
<li><i class="fa fa-angle-right"></i></li>
</ul>
What code do I need to incorporate to make this functional? Thanks in advance.
Django provides the module django.core.paginator for this purpose: https://docs.djangoproject.com/en/1.10/topics/pagination/ . Using this within Wagtail is very similar to the examples in the Django documentation - the only real difference is that when you're setting up the Paginator object to be passed to the template, you do that with a get_context method on the page model, instead of a view function. Your model definition will look something like this:
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
class ResourceIndexPage(Page):
# ...
def get_context(self, request):
context = super(ResourceIndexPage, self).get_context(request)
# Get the full unpaginated listing of resource pages as a queryset -
# replace this with your own query as appropriate
all_resources = ResourcePage.objects.live()
paginator = Paginator(all_resources, 5) # Show 5 resources per page
page = request.GET.get('page')
try:
resources = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
resources = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
resources = paginator.page(paginator.num_pages)
# make the variable 'resources' available on the template
context['resources'] = resources
return context
Within your template, you can now loop over the items using {% for resource in resources %}, and display the pagination links as follows:
<ul class="pagination">
{% if resources.has_previous %}
<li><i class="fa fa-angle-left"></i></li>
{% endif %}
{% for page_num in resources.paginator.page_range %}
<li {% if page_num == resources.number %}class="active"{% endif %}>{{ page_num }}</li>
{% endfor %}
{% if resources.has_next %}
<li><i class="fa fa-angle-right"></i></li>
{% endif %}
</ul>
I very much appreciate that you got me here - thanks so much for the assist. I had to make some adjustments to make it work. Here's the model if anyone comes across the same issue:
class NewsIndexPage(Page):
intro = RichTextField(blank=True)
def get_context(self, request):
context = super(NewsIndexPage, self).get_context(request)
# Get the full unpaginated listing of resource pages as a queryset -
# replace this with your own query as appropriate
blogpages = self.get_children().live().order_by('-first_published_at')
paginator = Paginator(blogpages, 3) # Show 3 resources per page
page = request.GET.get('page')
try:
blogpages = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
blogpages = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
blogpages = paginator.page(paginator.num_pages)
# make the variable 'resources' available on the template
context['blogpages'] = blogpages
return context
...and here's the HTML:
<ul class="pagination">
{% if blogpages.has_previous %}
<li>
<i class="fa fa-angle-left"></i>
</li>
{% endif %}
{% for page_num in blogpages.paginator.page_range %}
<li {% if page_num == blogpages.number %} class="active"{% endif %}>
{{ page_num }}
</li>
{% endfor %}
{% if resources.has_next %}
<li>
<i class="fa fa-angle-right"></i>
</li>
{% endif %}
</ul>
It works like a charm - and adds to the learning curve!
In case it's useful to anyone, I wanted this to work as closely as possible to the class-based view ListView, and so I ended up with this:
from django.core.paginator import Paginator, InvalidPage
from django.http import Http404
from django.utils.translation import gettext as _
from wagtail.core.models import Page
class ArticleListPage(Page):
# Some Page variables set here. #
# Pagination variables:
paginator_class = Paginator
paginate_by = 10
page_kwarg = 'page'
paginate_orphans = 0
allow_empty = False
def get_context(self, request):
context = super().get_context(request)
queryset = Page.objects.live()
paginator, page, queryset, is_paginated = self.paginate_queryset(
queryset, self.paginate_by, request)
context.update({
'paginator': paginator,
'page_obj': page,
'is_paginated': is_paginated,
'object_list': queryset,
})
return context
def paginate_queryset(self, queryset, page_size, request):
"""
Adapted from the ListView class-based view.
Added the request argument.
"""
paginator = self.paginator_class(
queryset,
self.paginate_by,
orphans=self.paginate_orphans,
allow_empty_first_page=self.allow_empty)
page_kwarg = self.page_kwarg
page = request.GET.get(page_kwarg) or 1
try:
page_number = int(page)
except ValueError:
if page == 'last':
page_number = paginator.num_pages
else:
raise Http404(_("Page is not 'last', nor can it be converted to an int."))
try:
page = paginator.page(page_number)
return (paginator, page, page.object_list, page.has_other_pages())
except InvalidPage as e:
raise Http404(_('Invalid page (%(page_number)s): %(message)s') % {
'page_number': page_number,
'message': str(e)
})
This will give you the same paginator, page_obj, is_paginated and object_list variables in your template that you would get with a normal Django ListView.
(Using python 3, Django 2.1 and Wagtail 2.3.)
In my code, I am writing an action for grouping, I would like to ask the user how many people would they like per group and then respond with an alert box that says something along the lines of you have 4 groups, based on user input. How do I do this in django admin, how do I create some kind of pop up that asks for the amount of people that they would like to put in a group? (I'm trying to achieve this with an action)
admin.py:
Def howmany (modeladmin, request, queryset):
people = queryset.count()
amount_per = [the number that the user inputs]
Amount_of_groups = people/amount_per
A more simplified and better approach I found here:
You just need to create an Action Form like this.
from django.contrib.admin.helpers import ActionForm
from django import forms
class XForm(ActionForm):
x_field = forms.ModelChoiceField(queryset=Status.objects.all(), required=False)
Now, define that XForm in your admin.py
class ConsignmentAdmin(admin.ModelAdmin):
action_form = XForm
actions = ['change_status']
def change_status(modeladmin, request, queryset):
print(request.POST['x_field'])
for obj in queryset:
print(obj)
change_status.short_description = "Change status according to the field"
admin.py Something like:
class MyAdmin(admin.ModelAdmin):
def howmany(modeladmin, request, queryset):
people = queryset.count()
amount_per = [the number that the user inputs]
Amount_of_groups = people/amount_per
if 'apply' in request.POST:
form = AmountPerForm(request.POST)
if form.is_valid():
amount_per = form.cleaned_data['amount_per']
self.message_user(request, u'You selected - %s' % amount_per)
return HttpResponseRedirect(request.get_full_path())
else:
form = AmountPerForm()
return render(request, 'admin/amount_per_form.html', {
'items': queryset.order_by('pk'),
'form': form,
'title': u'Your title'
})
File "admin/amount_per_form.html" contains something like:
{% extends 'admin/base_site.html' %}
{% block content %}
<form action="" method="post">
{% csrf_token %}
<input type="hidden" name="action" value="howmany" />
{% for item in items %}
<input type="hidden" name="_selected_action" value="{{ item.pk }}"/> {# thanks to #David Hopkins for highligting it #}
{% endfor %}
{{ form }}
<p>Apply for:</p>
<ul>{{ items|unordered_list }}</ul>
<input type="submit" name="apply" value="Apply" />
</form>
{% endblock %}
I have a page that display all the objects acorrding to the catergory the students pick.
I implemented a pagination on the site to split the objects at different pages.
The problem occurs when the students pick a catergory from the dropbox and tries to flick through the pagination for new and old entries.
The reason this happens because everytime the user picks a catergory from the dropbox , the dropbox get reset once it retrieve the objects. So when users try to flick through objects using the pagination . The pagination doesn't know what data to retrieve because the dropbox catergory get reset and redirec the users to a blank page.
A solution to this is to program the dropbox to remain static for the choices the users make so when the users flicks through the data split by the pagination , the pagination know can retrieve objects according to the dropbox.
I can't figure out how to make this dropbox remain static for the choices the users make.
my views.py
def BoardFinder(request):
form = BoardFinderForm(request.POST)
fo = BoardFinderForm()
if form.is_valid():
Category = form.cleaned_data['Category']
posts = Board.objects.filter(Category=Category)
paginator = Paginator(posts, 1)
try: page = int(request.GET.get("page", '1'))
except ValueError: page = 1
try:
posts = paginator.page(page)
except (InvalidPage, EmptyPage):
posts = paginator.page(paginator.num_pages)
return render(request,"boardfinder.html",{"posts":posts,"fo":fo})
return render(request,"boardfinder.html",{"fo":fo})
My models.py
class Board(models.Model):
MATH = 'MATH'
ENGLISH = 'ENGLISH'
SCIENCE = 'SCIENCE'
LANGUAGE = 'LANGUAGE'
CATEGORY = (
(MATH, 'Math'),
(ENGLISH, 'English'),
(SCIENCE, 'Science'),
(LANGUAGE, 'Language'),
)
Category =models.CharField(max_length=30,choices=CATEGORY)
user = models.ForeignKey(User)
name = models.CharField(max_length=100)
created = models.DateTimeField(auto_now_add=True)
picture = models.OneToOneField('Picture',related_name='picture',blank=True,null=True)
def __unicode__(self):
return self.name
class BoardFinderForm(forms.ModelForm):
class Meta:
model = Board
fields = ('Category',)
Important parts of my boardfinder.html
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ fo.as_p }}
<input type = "submit" value= "Find WhiteBoard" />
</form>
{% for post in posts.object_list %}
<div class="title">{{ post.name }}</div>
{% endfor %}
<form method="GET">
<p><select name="category">
<option value=""
{% if not request.session.category %}selected{% endif %}>
(All subjects)
</option>
<option value="ENGLISH"
{% if request.session.category == "ENGLISH" %}selected{% endif %}>
English
</option>
<option value="LANGUAGE"
{% if request.session.category == "LANGUAGE" %}selected{% endif %}>
Language
</option>
<option value="MATH"
{% if request.session.category == "MATH" %}selected{% endif %}>
Math
</option>
<option value="SCIENCE"
{% if request.session.category == "SCIENCE" %}selected{% endif %}>
Science
</option>
</select></p>
<input type = "submit" value= "Find WhiteBoard" />
</form>
def BoardFinder(request):
category = request.GET.get('category')
if category:
request.session['category'] = category
posts = Board.objects.filter(Category=category)
paginator = Paginator(posts, 1)
try: page = int(request.GET.get("page", '1'))
except ValueError: page = 1
try:
posts = paginator.page(page)
except (InvalidPage, EmptyPage):
posts = paginator.page(paginator.num_pages)
return render(request,"boardfinder.html",{"posts":posts,"fo":fo})
return render(request,"boardfinder.html",{"fo":fo})