How can django-cms plugins be displayed using pagination - django

I written two django-cms plugins to display image galleries and videos.
These are attached to CMS pages at /gallery/ and /videos/ where each template has a placeholder allowing the corresponding plugin to be included.
At that base level where I have gallery.html and video.html rendering all plugin instances to the page I would like to be able to attach endless for pagination.
This was a really simple task to achive pagination on images within a gallery because I have an apphook view in the gallery to collect a list of all the images and then it's as simple as {% pagingate images %} {% for image in images %} etc in the template.
However in a template where django-cms controls the collection & rendering of all the plugin instances and I lose that control, how can I paginate the plugins?
I've started down a route of using an apphook on the /gallery/ index, but to acomplish this I can imagine I'll need to stop django-cms doing what it does by default and what it should be left to do. So I need some guidance/advise on the best method for the job. Anyway, here's some code;
# views.py
def gallery_index(request, *args, **kwargs):
template = request.current_page.template
placeholder_id = request.current_page.placeholders.get(slot='gallery').id
gallery = Placeholder.objects.get(id=placeholder_id)
galleries = gallery.cmsplugin_set.all()
return render_to_response(
template,
{'galleries': galleries},
context_instance=RequestContext(request)
)
# gallery.html
<h3>GALLERIES</h3>
<div id="panel-area" class="gallery_grid">
<ul id="galleries" class="gridview" style="width: 800px;">
{% paginate galleries %}
{% for gallery in galleries %}
<li>{{ gallery }}</li> <!-- testing the pagination -->
{% endfor %}
{% placeholder "gallery" %}
</ul>
{% show_pages %}
# cms_plugins.py
class ImageGalleryPlugin(CMSPluginBase):
name = _('Image Gallery')
model = ImageGalleryPlugin
form = ImageGalleryAdminForm
render_template = 'single_gallery.html'
def render(self, context, instance, placeholder):
context.update({'gallery': instance,})
return context
# single_gallery.html
<li class="gallery_top">
<span class="title">
<a href="{% url 'image_gallery_page' page_id=request.current_page.id gallery_id=gallery.id %}">{{ gallery.event_date|date:"d/m/Y" }}<br/>
<p class="sub-text">View more of {{ gallery.event_name }}</p></a>
</span>
<img src="{{ gallery.display_random.gallery_display }}" alt="" border="0" />
</li>
My current solution is a jQuery solution, but I'd love it all to be controlled by django endless pagination for consistency in behaviour and design.

Been there. Pagination in Django CMS plugin is indeed problematic.
One possible way to solve the problem
Implement paginated API endpoint for fetching the objects. Use django.views.generic.list.ListView for example. It has nice built in pagination.
In CMS plugin, fetch objects from the API endpoint with AJAX. CMS plugin doesn't need to know which objects to render, it just needs to know where it can fetch the objects (the API endpoint).
This approach requires frontend code for fetching the correct objects, updating the UI, keeping track of the current page and so on, but can be (and has been) successfully used to implement paginated Django CMS plugins.

Here is my quick solution to add pagination into a django cms plugin
from cms.models.pagemodel import Page
from cms.plugin_base import CMSPluginBase
from django.http import Http404
from django.core.paginator import InvalidPage, Paginator
class MyPlugin(CMSPluginBase):
def render(self, context, instance, placeholder):
query_set = Page.objects.filter(is_page_type=False)
page = context['request'].GET.get('page') or 1
paginator, page, queryset, is_paginated = self.paginate_queryset(page, query_set, 6)
context.update({'paginator': paginator,
'page_obj': page,
'is_paginated': is_paginated,
'object_list': queryset
})
return context
def paginate_queryset(self, page, queryset, page_size):
paginator = Paginator(queryset, page_size)
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)
})

Related

django, views direct to a another html page

I am using Django for develop a website. The website is intended to use to search information stored in a MySQL database.
This is the current basic flow of the web site.
1) index.html - this has a form to select an option
2) according the option, users will redirect to search.html (include a form)
3) once the user provides the criteria, the result will be displayed in reply.html
In my views.py , I have two functions.
from django.shortcuts import render
from website.models import WebsiteRepository
from .forms import SearchForm
from .forms import SelectTypeForm
def Search(request):
if request.method == 'POST':
#do something
return render(request, 'reply.html', {'env_dict':env_dict})
else:
#do something
return render(request, 'search.html', context = context)
def index(request):
if request.method =='POST':
#do something
return render(request, 'search.html', context = context)
else:
#do something
return render(request, 'index.html', context= context)
When I go to index.html page, I can select a option and it will direct me to search.html. After, I fill the form there and submit, it wont give me the reply.html page.
I have a feeling that, I could make this work by changing urls.py.
from django.urls import path
from website import views
urlpatterns = [
path('', views.index, name='index'),
#path('search/', view.Search, name ='Search')
]
I tried to google it. But its too much details and Iam kind of lost.
Do any of you guys know how to achieve this?
Thanks
search.html
{% extends "base_generic.html" %}
{% block content %}
<h3>Welcome to search information Repository</h3>
<form method="post">
{% csrf_token %}
{{form.as_p}}
<button type = 'submit'>submit</button>
</form>
{% endblock %}
index.html
{% block content %}
<h3>Welcome to information Repository</h3>
<form method="post">
{% csrf_token %}
{{form.as_p}}
<button type = 'submit'>submit</button>
</form>
just for clarify things more, ill add the forms.py too
from django import forms
from .models import WebsiteRepository
class SearchForm(forms.Form):
websiterepository = WebsiteRepository
env_indicators = websiterepository.objects.filter (key_aspect='Environmental').values_list('repo_id','indicator')
indicator = forms.ChoiceField(choices=env_indicators,label = 'Indicator' )
OPTIONS = (('2000','2000'),('2001','2001'),('2002','2002'), ('2003','2003'),('0000','0000'),)
year = forms.ChoiceField(choices=OPTIONS)
class SelectTypeForm(forms.Form):
OPTIONS = (('1', 'Envirnmental Indicators'),('2','Economic Indicators'),('3','Social Indicators'),)
types = forms.ChoiceField(choices=OPTIONS)
Your code is wrong on many points.
First thing first: for a search, you want a GET request, not a POST (POST is for updating the server's state - adding or updating your database mostly). This is the semantically correct method (since you want to GET data), and it will allow a user to bookmark the url.
Second point: you don't want to submit the search form to the index view but to the search view. No need for redirects etc, just use the {% url %} templatetag to fill the action attribute of your form (you of course need to have a 'Search' url in your urls.py):
<form method="get" action="{% url 'Search' %}">
{% csrf_token %}
{{form.as_p}}
<button type = 'submit'>submit</button>
</form>
if you want to have this form on more than one page (which is often the case for search forms), use an inclusion tag tha will take care of creating an unbound SearchForm and render the template fragment.
Then in your search view, you only want GET requests, and do not use two different templates, this will only lead to useless duplication.
def Search(request):
form = SearchForm(request.GET)
# use the form's data - if any - to get search results
# and put those results (even if empty) in you context
return render(request, 'reply.html', {'env_dict':env_dict})
And finally, your search form is totally broken:
class SearchForm(forms.Form):
# this is totally useless
websiterepository = WebsiteRepository
# this will only be evaluated once at process startup, so you will
# get stale data in production - and probably different data
# per process, in a totally unpredictable way.
# You need to either put this in the form's __init__ or wrap it
# in a callable and pass this callable
env_indicators = websiterepository.objects.filter (key_aspect='Environmental').values_list('repo_id','indicator')
indicator = forms.ChoiceField(choices=env_indicators,label = 'Indicator' )
# are you going to manually add a new year choice every year ???
OPTIONS = (('2000','2000'),('2001','2001'),('2002','2002'), ('2003','2003'),('0000','0000'),)
year = forms.ChoiceField(choices=OPTIONS)
For the "indicators" ChoiceField you want something like:
def get_indicators_choices():
return Websiterepository.objects.filter (key_aspect='Environmental').values_list('repo_id','indicator')
class SearchForm(forms.Form):
# IMPORTANT : we are NOT calling the function here, just
# passing it (python functions are objects) to the field, which
# will call it everytime the form is instanciated, so you don't
# have stale data
indicator = forms.ChoiceField(
choices=get_indicator_choices,
label='Indicator')
As a last note: be consistent with your namings (ie why name one view in all lower (index) and capitalize the other (Search) ? Whichever convention you choose (I strongly suggest respecting pep8 here), at least stick to it for the whole project.
The problem is that code is not redirecting to /search, instead rendering search.html after post from index.html.
Try doing like-
views.py-
#your code
def index(request):
#do something
if request.method == 'POST':
return redirect('Search')
else:
#render index.html
def search(request):
#do something
if request.method == 'POST':
#render reply.html
else:
#render search.html
Another way to achieve this is if you specify action in your form so that form posts on /search.
search.html
<form method="post" action="/search">
{% csrf_token %}
{{form.as_p}}
<button type = 'submit'>submit</button>
</form>

Django CMS Aldryn NewsBlog delete Articles from the frontend

I'm trying to get the standard Aldryn Newsblog Buttons working in my Frontend Page. So every User can Add, Delete and Edit Articles(only the articles they created themselves but thats not the question). This is the Menu with the links:
Menu in the Toolbar
So i want to add a Button in my template wich triggers the edit, add or delete prompt: Delete prompt
I hope someone can help me. Thanks in advance.
If you really don't want all employees to see the toolbars, then you're taking on quite a bit of extra work. I would still consider this as an option, as you can apply permissions so that a user can only edit the content you allow, which means that users can take full advantage of Django CMS's built in functionality, which is great.
If you still don't want to take this route then you're going to have to build your own mini admin for your article model. Below I've quickly thrown together an idea for how you can approach this to hopefully help point you in the right direction.
First, your article view should be something like:
from django.views.generic import DetailView
from .models import Article
class ArticleView(DetailView):
context_object_name = 'article'
model = Article
template_name = 'path/to/article.html'
def get_context_data(self, **kwargs):
context = super(ArticleView, self).get_context_data(**kwargs)
context['show_controls'] = (self.request.user.is_authenticated() and
context[self.context_object_name].article == self.request.user)
return context
With the article template like:
<section>
{% if show_controls %}
<div class="controls">
Delete
Edit
</div>
{% endif %}
<article>
...
</article>
</section>
The path to delete view could be a confirm page like the Django admin. So you'd have a view like:
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404, redirect, render
from .models import Article
#login_required
def delete_article(request, article_pk):
if request.method == "POST":
article = get_object_or_404(Article, pk=article_pk)
if request.user != article.author:
raise PermissionDenied
article.delete()
return redirect('/redirect/url')
else:
context = {}
...
return render(request, 'path/to/confirm/delete.html', context)
With a template along the lines of:
<section>
<form method="POST">
{% csrf_token %}
<p>Are you sure you want to delete?</p>
<input type="submit" value="Delete">
</form>
</section>
You'd then create a similar setup for the edit page, navigate the user to a page that has a form where the fields can be amended and submitted etc.

Django using more than one forms in one page from same model

I want to use more than one forms in the same page from the same model.
Ok, lets take it easy.
I have Social modules that have 3 attributes (network, url and image) charfield.
I've added 4 values in Social database (Facebbok, Twitter, Youtube, Pinterest)
In the settings view (settings.html) i want to have all 4 forms (for Facebook, Twitter etc.) to edit them.
Something like that:
Facebook: text input (that contains the current facebook url)
Youtube: text input (that contains the current youtube url)
etc.
So when i go to settings.html i can change, update social url for all networks.
I have something like this for General Settings module (that have 3 fields, Title, Slug, Description and 1 attribute cuz the website have 1 title, 1 slug and 1 description). For this one is pretty simple i can use get_object_or_404 because Settings module have just 1 value and i can select it.. but the problem is Social Module have more values and i want to have on my page all forms from them so i can edit how ever i want.
views.py
def settings(request):
sidebar_items = Sidebar.objects.order_by('position')
social_items = Social.objects.order_by('network')
settings = get_object_or_404(Settings, pk = 1)
if request.method == "POST":
form_settings = SettingsForm(request.POST, instance = settings)
if form_settings.is_valid():
settings = form_settings.save(commit = False)
settings.save()
return HttpResponseRedirect('/dashboard/settings')
else:
form_settings = SettingsForm(instance = settings)
context = {'sidebar_items' : sidebar_items, 'form_settings' : form_settings, 'social_items' : social_items}
return render(request, 'dashboard/settings.html', context)
Django doesn't care how many forms you want to initialize in your view. If they're for the same model, you can use a formset. Otherwise, you can initialize and create objects however you want.
Example:
def your_view(request):
social_items = Social.objects.order_by('network')
forms = []
for index, social_item in enumerate(social_items):
forms.append(SocialForm(request.POST or None, instance=social_item,
prefix="form_{}".format(index)))
if request.method == 'POST':
for form in forms:
if form.is_valid():
form.save()
# do whatever next
return render(request, 'some-template.html', {'forms': forms})
You don't need three separate form tags in your template. You can submit all of the data in one post. Django will try to hydrate an instance of each model from the POST data, and return any errors if that fails.
In your template, you'll need to iterate over the form instances:
# some-template.html
<form action="." method="post" enctype="x-www-form-urlencoded">
{% for form in forms %}
<ol>
<li>
{{ form.network }}
{{ form.network.errors }}
</li>
<li>
{{ form.url }}
{{ form.url.errors }}
</li>
<li>
{{ form.image }}
{{ form.image.errors }}
</li>
</ol>
{% endfor %}
<button type="submit">Save</button>
</form>

Paginating a Detail view in Django

I'm new to Django and for my first project I'm building a portfolio. And I need a little kick-start with pagination help. I have an "index" view with a list of projects and a detail view of each project. In the detail view, I want a feature to be able to paginate between each individual object. I've gone through the Pagination documenation and applied what I learned with my index view but when I try to do the same thing with my detail view I get a TypeError saying my "object of type 'Project' has no len().
Here's a sample of my views.py code for reference:
def index( request ):
all_projects = Project.objects.all().order_by( '-pub_date' )
paginator = Paginator( all_projects, 12 )
try:
page = int( request.GET.get( 'page','1' ))
except ValueError:
page = 1
try:
projects = paginator.page( page )
except (EmptyPage,InvalidPage):
projects = paginator.page( paginator.num_pages )
return render_to_response( 'portfolio/index.html', { 'all_projects':all_projects, 'projects':projects, 'MEDIA_URL':MEDIA_URL })
def detail( request, project_id ):
project = get_object_or_404( Project, id=project_id )
return render_to_response( 'portfolio/detail.html', { 'project':project, 'MEDIA_URL':MEDIA_URL } )
Apologies if I sound n00b-ish because I am, and gratitude in advance for any help. Also, I read this previous post but it didn't seem to apply to me because my views aren't Class-based.
Django has built-in get_next_by_FOO()/get_previous_by_FOO() methods which will return the next/previous object depeding on a datetime-field.
You could access them in the template by somethow like:
{{ project.get_next_by_pub_date.title }}
I would say that this is the preferred method over using pagination for that, as you will get a nice url you can define in your model's get_absolute_url for every item!
To paginate you need an instance of a QuerySet, not an object! So you should replace your get_object_or_404 call by a filter/all. So it would be basically the same as the list view, but just pass the number 1 to the paginator, as you already do!
I actually do not have that setup through Django's pagination. I used this code:
prev_issue = Issue.objects.filter(title=title).filter(number__lt=issue.number).order_by('-number')[:1]
next_issue = Issue.objects.filter(title=title).filter(number__gt=issue.number).order_by('number')[:1]
In your case, I would do something like this, but you would have to filter by something, too:
prev_project = Project.objects.order_by('-pub_date')[:1]
next_project = Project.objects.order_by('pub_date')[:1]
Then put the two in the context.
I also recommend django-pagination. I notice you probably want the pagination to stay on the index, correct? http://code.google.com/p/django-pagination/
You just have to mess with the template codes and it works great.
The great thing about Django is, that there is code for nearly every common programming problem, that you can download and use in your own project. (and most of the django packages are quiet well written too)
for pagination check out http://pypi.python.org/pypi/django-pagination
it's really easy to install and setup, so you do not have to think about pagination and focus on coding your software!
hope this helps, Anton
In my views.py
def ProjectDetail(request,pk):
context = {}
template = 'views/projectdetail.html'
project = ''
prev = Project.objects.filter(pk__lt=pk).order_by('-pk')[:1]
next = Project.objects.filter(pk__gt=pk).order_by('pk')[:1]
try:
print(prev[0].pk)
print(next[0].pk)
except:
pass
project = Project.objects.filter(pk=pk)
context['categories'] = ProjectCategory.objects.all()
paginator = Paginator(project, 1) # Show 25 contacts per page
page = request.GET.get('page')
try:
data = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
data = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
data = paginator.page(paginator.num_pages)
if prev:
context['prev'] = prev[0].pk
if next:
context['next'] = next[0].pk
context['data'] = data
return render_to_response(template, context,context_instance=RequestContext(request))
in my template i have
<div class="row">
<a {% if next %} href="{% url 'task:project-detail' next %}" class="btn btn-primary pull-right" {% else %} class="btn btn-primary pull-right disabled" {% endif %}>Next</a>
<a {% if prev %} href="{% url 'task:project-detail' prev %}" class="btn btn-primary pull-left" {% else %} class="btn btn-primary pull-left disabled" {% endif %} >Previous</a>
</div>

Pagination problem

I'm using this code for my pagination, and I'd like the user's choice to be persistent throughout the site (this has been solved so far)...the only problem now is that the session variable now is permanent until the session is cleared by closing the browser. Also, how can I get the adjacent pages displayed...like in the digg-style Django paginator. I haven't been able to make sense of how to implement this into my code.
The code is as follows:
from django.core.paginator import Paginator, InvalidPage, EmptyPage
def paginate(request, object_list, paginate_by=10):
try:
if "per_page" in request.session:
per_page = request.session["per_page"]
else:
request.session["per_page"] = int(request.REQUEST['p'])
per_page = request.session["per_page"]
request.session.set_expiry(0)
except:
per_page = 10
paginator = Paginator(object_list, per_page)
try:
page = int(request.GET.get('page', '1'))
except ValueError:
page = 1
try:
items = paginator.page(page)
except (EmptyPage, InvalidPage):
items = paginator.page(paginator.num_pages)
return items
Then in my template I have this to render the pagination links:
<div class="pagination" align="center">
<span class="step-links">
{% if items.has_previous %}
previous
{% endif %}
<span class="current">
Page {{ items.number }} of {{ items.paginator.num_pages }}
</span>
{% if items.has_next %}
next
{% endif %}
</span>
</div>
You can achieve this by enabling sessions.
I recommend reading through the chapter Sessions, Users, and Registration on the djangobook website.
Edit: Now that you've enabled sessions, I think the problem is the hyperlinks in the template. Use an ampersand to separate multiple parameters in a url, for example
next
Edit 2: I'm not sure if I understand what the problem is with the session expiry. The line that sets the session to expire when the browser closes is request.session.set_expiry(0). See the django docs on Using Sessions in views if you want to change that.
To make a Digg style paginator, you need to write a function that takes the current page number and the total number of pages, and returns a list of page numbers. Then, in the template, loop through the page numbers and construct links to the pages.
A list of lists of page numbers would allow you to split the page numbers into groups, eg
[[1,2], [20,21,22,23,24], [30,31]]