I have a different design on the first page of a list, and need 8 items on the first page. On the rest I would like 9 items. I have tried to specify items on the first page, but the second page thinks the 9.th item was shown on the first page, it was not. Anyone know if it is possible to fix this?
class EntryList(ListView):
model = Entry
def get_paginate_by(self, queryset):
page_kwarg = self.page_kwarg
page = self.kwargs.get(page_kwarg) or self.request.GET.get(page_kwarg) or '1'
if page == '1':
self.paginate_by = 8
else:
self.paginate_by = 9 #This assumes the 9.th was shown on the first page
return self.paginate_by
Related
I'm working on an app where a user can select a category, which will return a random selection from that category. The main functionality I'm trying to implement is once an item is selected, it can no longer be randomly selected in session.
For example, we have 3 categories of photos: landscape, urban, and portraits, each with 5 photos. A user selects urban, is then redirected to a details page with a random photo from urban category. He can either refresh the page or click a button to get a new photo from that category. When that category is out of new photos, he is redirected home.
I am able to get my random item from the selected category through converting a queryset to a list, but the data isn't persisting. On every refresh the list I have resets, thus a previously selected photo can come up again, ignoring the the fact that I removed the item from the list after it was selected.
Here's the views.py with the function responsible for this:
def randomPhoto(request, pk, **kwargs):
# queryset to get all photos from selected category
gallery = list(Photos.objects.filter(id=pk)
.values_list("partof__category", flat=True))
# select random photo from list
last = len(gallery) -1
randomInt = random.randint(0, last)
randomPic = gallery[randomInt]
gallery.remove(randomPic)
if len(gallery) == 0:
return render(request, 'gallery/category_select.html')
photoDetails = {
'category' : Category.objects.get(id=pk),
'author' : Author.objects.get(tookin__category=randomPic),
'uploadedPhoto' : 'http://localhost:8000/media/' +
str(Photo.objects.get(category=randomPic).photoUpload),
'randomPic' : randomPic,
}
return render(request, 'gallery/random_photo.html', {'photoDetails': photoDetails})
The functionality I'm looking for is (where each number is an object/item in list):
User selects urban category:
urban has the following items: [1, 2, 3, 4, 5]
random [3] selected from urban
urban now has [1, 2, 4, 5]
User refreshes:
random [4] selected
urban now has [1, 2, 5]
User refreshes:
random [2] selected
urban now has [1, 5]
User refreshes:
random [5] selected
urban now has [1]
User refreshes:
random [1] selected
urban now has []
User is redirected home
I believe my problem lies in having to configure either sessions or cookies to have the data persist in an anonymous session. Eventually I will be adding a Users module so each user will have their browsing history saved but for now I want it to work just as an anonymous user.
I've tried adding SESSION_SAVE_EVERY_REQUEST = True to settings.py and placing request.session.modified = True in my views.py, though I doubt I'm implementing them properly. I've read some SO questions on sessions and cookies but wasn't able to find something to work with my issue. The Django Sessions Doc seemed interesting but overwhelming. I'm not sure where to begin trying to experiment with wiring the sessions aspect together.
I am wondering if there's an easy/Pythonic way to achieve having my web app give me a non-repeating item from a list until none are left within the session.
Your issue is that your variable is not carried over from one request to the next. The best way to do this would be to use request.session = ... to set a variable, and then check it later and perform actions. Here is an example that you can expand on to make it to your liking:
import random
from django.shortcuts import redirect
class TestView(View):
def get(self, request, *args, **kwargs):
gallery = request.session.get('gallery', None)
if (type(gallery) is list) and (len(gallery) == 0): # When list is empty, clear session & then redirect
del request.session['gallery']
request.session.modified = True
return redirect('<your_redirect_url>')
if gallery is None: # If first visit to page, create gallery list
gallery = list(models.Photos.objects.all().values_list("partof__category", flat=True))
# select random photo from list
last = len(gallery) -1
randomInt = random.randint(0, last)
randomPic = gallery[randomInt]
gallery.remove(randomPic)
request.session['gallery'] = gallery
return render(request, 'test.html', {})
I've got a website that shows photos that are always being added and people are seeing duplicates between pages on the home page (last added photos)
I'm not entirely sure how to approach this problem but this is basically whats happening:
Home page displays latest 20 photos [0:20]
User scrolls (meanwhile photos are being added to the db
User loads next page (through ajax)
Page displays photos [20:40]
User sees duplicate photos because the photos added to the top of the list pushed them down into the next page
What is the best way to solve this problem? I think I need to somehow cache the queryset on the users session maybe? I don't know much about caches really so a step-by-step explanation would be invaluable
here is the function that gets a new page of images:
def get_images_paginated(query, origins, page_num):
args = None
queryset = Image.objects.all().exclude(hidden=True).exclude(tags__isnull=True)
per_page = 20
page_num = int(page_num)
if origins:
origins = [Q(origin=origin) for origin in origins]
args = reduce(operator.or_, origins)
queryset = queryset.filter(args)
if query:
images = watson.filter(queryset, query)
else:
images = watson.filter(queryset, query).order_by('-id')
amount = images.count()
images = images.prefetch_related('tags')[(per_page*page_num)-per_page:per_page*page_num]
return images, amount
the view that uses the function:
def get_images_ajax(request):
if not request.is_ajax():
return render(request, 'home.html')
query = request.POST.get('query')
origins = request.POST.getlist('origin')
page_num = request.POST.get('page')
images, amount = get_images_paginated(query, origins, page_num)
pages = int(math.ceil(amount / 20))
if int(page_num) >= pages:
last_page = True;
else:
last_page = False;
context = {
'images':images,
'last_page':last_page,
}
return render(request, '_images.html', context)
One approach you could take is to send the oldest ID that the client currently has (i.e., the ID of the last item in the list currently) in the AJAX request, and then make sure you only query older IDs.
So get_images_paginated is modified as follows:
def get_images_paginated(query, origins, page_num, last_id=None):
args = None
queryset = Image.objects.all().exclude(hidden=True).exclude(tags__isnull=True)
if last_id is not None:
queryset = queryset.filter(id__lt=last_id)
...
You would need to send the last ID in your AJAX request, and pass this from your view function to get_images_paginated:
def get_images_ajax(request):
if not request.is_ajax():
return render(request, 'home.html')
query = request.POST.get('query')
origins = request.POST.getlist('origin')
page_num = request.POST.get('page')
# Get last ID. Note you probably need to do some type casting here.
last_id = request.POST.get('last_id', None)
images, amount = get_images_paginated(query, origins, page_num, last_id)
...
As #doniyor says you should use Django's built in pagination in conjunction with this logic.
I have a site that basically displays a huge table of people that I want to be able to filter, sort and page through. I want to do this in multiple views that have different preexisting constraints on which people are displayed in the table. My problem is that, if I filter by state for example, sort by name, and then try to go to the next page, it resets. For example, if I filter once I get my/url/2013/?sort=n&state=MN&page=1; then if I go to page 2, I just get my/url/2013/?page=2. It doesn't remember what I've already asked it.
def all(request, year=default_year):
#sorting (sort_options was defined prior to this view)
if 'sort' in request.GET:
sort = request.GET.get('sort')
order = sort_options[sort]
else:
order = '-score'
players = Player.objects.filter(year=year).order_by(order)
url_query = ''
#filtering
if 'position' in request.GET:
filterData = _filter(request,players)
players = filterData['players']
url_query += filterData['url_query']
# pagination
paginator = Paginator(players,25)
page = request.GET.get('page')
try:
players = paginator.page(page)
except PageNotAnInteger:
players = paginator.page(1)
except EmptyPage:
players = paginator.page(paginator.num_pages)
data = {'players':players,'url_query':url_query}
return render_to_response('all.html', data, context_instance = RequestContext(request))
In my template I reference url_query like so:
<th class="name">Name</th>
and like so:
Next
This is pretty obviously wrong to me but I don't know how to do it right.
When you render your template you can pass to the context (in your case data) object sort parameter and then in the template you can use url to show which view to invoke.
I have a view which displays a list items.
def edit_order(request, order_no):
try:
status_list = models.Status.objects.all()
order = models.Order.objects.get(pk = order_no)
if order.is_storage:
items = models.StorageItem.objects.filter(orderstoragelist__order__pk = order.pk)
else:
items = models.StorageItem.objects.filter(orderservicelist__order__pk = order.pk)
except:
return HttpResponseNotFound()
I want to put these list of item in another view. Unfortunately this is proving to be trickier then I thought.
#login_required
def client_items(request, client_id = 0):
client = None
items = None
try:
client = models.Client.objects.get(pk = client_id)
items = client.storageitem_set.all()
item_list = models.StorageItem.objects.filter(orderstoragelist__order__pk = order.pk)
except:
return HttpResponse(reverse(return_clients))
return render_to_response('items.html', {'items':items, 'client':client, 'item_list':item_list}, context_instance = RequestContext(request))
I thought maybe I can just paste the definition of items and just call that item_list but that does not work. Any ideas
items.html
{% for item in item_list %}
{{item.tiptop_id}
{% endfor %}
From your comment:
I get a white screen with the url printed on the screen. /tiptop/client in this case.
Because that's what you've asked for:
except:
return HttpResponse(reverse(return_clients))
This means that if there are any bugs or problems in the above, your view will simply output a response containing just that URL. Maybe you meant to use HttpResponseRedirect, so the browser actually redirects to the URL - but still you should not use a blank except, as it prevents you from seeing what is actually going wrong.
To answer the main question, think about what your edit_order view returns: it gives you a complete HTML response with a rendered template. How could you use that as an element in a query in another view? You need to think logically about this.
One possible solution would be to define a separate function which just returns the data you want - as a plain queryset - and both views can call it. Does that do what you want?
I'd like to implement pagination such that I can allow the user to choose the number of records per page such as 10, 25, 50 etc. How should I go about this? Is there an app I can add onto my project to do this?
Thanks
Django has a Paginator object built into core. It's a rather straightforward API to use. Instantiate a Paginator class with two arguments: the list and the number of entries per "page". I'll paste some sample code at the bottom.
In your case you want to allow the user to choose the per-page count. You could either make the per-page count part of the URL (ie. your/page/10/) or you could make it a query string (ie. your/page/?p=10).
Something like...
# Assuming you're reading the Query String value ?p=
try:
per_page = int(request.REQUEST['p'])
except:
per_page = 25 # default value
paginator = Paginator(objects, per_page)
Here's some sample code from the Django doc page for the Paginator to better see how it works.
>>> from django.core.paginator import Paginator
>>> objects = ['john', 'paul', 'george', 'ringo']
>>> p = Paginator(objects, 2)
>>> p.count
4
>>> p.num_pages
2
>>> p.page_range
[1, 2]
>>> page1 = p.page(1)
>>> page1
<Page 1 of 2>
>>> page1.object_list
['john', 'paul']
google on "django pagination" and make sure to use "covering index" in your SQL for efficient query.
T. Stone's answer covers most of what I was going to say. I just want to add that you can use pagination in Generic Views. In particular, you may find django.views.generic.list_detail.object_list useful.
You can write a small wrapper function that gets the number of objects to display per page from the request object, then calls object_list.
def paginated_object_list(request, page):
my_queryset=MyModel.objects.all()
#Here's T. Stone's code to get the number of items per page
try:
per_page = int(request.REQUEST['p'])
except:
per_page = 25 # default value
return object_list(request, queryset=my_queryset,
paginate_by=per_page, page=page)
Then, the context for your template will contain the variables,
paginator: An instance of django.core.paginator.Paginator.
page_obj: An instance of django.core.paginator.Page.
and you can loop through page_obj to display the objects for that page.
What does need? Well.
You can add custom control for change_list.html, for pagination block for example.
This will be reload list page with get parameter per_page for example with certain value onchange event.
For your adminModel you must override changelist_view method where you must handle get parameter and set this value as list_per_page field value.
def changelist_view(self, request):
if request.GET.get('per_page') and int(
request.GET.get('per_page')) in CHANGELIST_PERPAGE_LIMITS:
self.list_per_page = int(request.GET.get('per_page'))
else:
self.list_per_page = 100
extra_context = {'changelist_perpage_limits': CHANGELIST_PERPAGE_LIMITS,
'list_per_page': self.list_per_page}
return super(mymodelAdmin, self).changelist_view(request, extra_context)
I use extra_context for access to this values into template. Maybe there is more neat approach to access i don't know :-)