def home(request, model="s", objectId="0", page_num=1, template='home.html'): paginate_by = settings.PAGINATE_BY #what we get as parameter is always a string page_num = int(page_num) if model == 'u': #for user we need to filter for the user info_list = Paginator(sol.objects.filter(author__username=objectId), paginate_by) elif model== 's': #for sol; home page if objectId == '0': info_list = Paginator(sol.objects.all(), paginate_by) else: info_list = Paginator(sol.objects.filter(id=objectId), paginate_by) elif model =='g': #for group if objectId == '0': info_list = Paginator(group.objects.all(), paginate_by) else: info_list = Paginator(sol.objects.filter(group=group.objects.get(id=objectId)), paginate_by) #if the user altered the URL for a particular page that doesn't exist try: page_info = info_list.page(page_num) except InvalidPage: page_num = 1 page_info = info_list.page(page_num) has_previous = page_info.has_previous() has_next = page_info.has_next() info_dict = { 'query_list' : page_info.object_list, 'has_previous' : page_info.has_previous(), 'previous_page' : page_info.previous_page_number(), 'has_next' : page_info.has_next(), 'next_page' : page_info.next_page_number(), 'site_name' : settings.SITE_NAME, 'user' : request.user, } if model == 's': form = solForm() #this is how you append to a dict info_dict['solForm'] = form if model == 'u': #this is how you append to a dict info_dict['u_id'] = objectId info_dict['nickname'] = userprofile.objects.get(user__username=objectId).nickname if model == 'g': info_dict['grpForm'] = grpForm() if objectId == '0': info_dict['solForm'] = solForm() else: info_dict['solForm'] = solForm(initial={'group': group.objects.get(id=objectId)}) info_dict['grpId'] = objectId info_dict['grpName'] = group.objects.get(id=objectId).desc return render_to_response(template, info_dict)
def list( self, request, queryset, page=None, ): """ Renders a list of model objects to HttpResponse. """ template_name = '%s/%s_list.html' % (self.template_dir, queryset.model._meta.module_name) if self.paginate_by: paginator = QuerySetPaginator(queryset, self.paginate_by) if not page: page = request.GET.get('page', 1) try: page = int(page) object_list = paginator.page(page).object_list except (InvalidPage, ValueError): if page == 1 and self.allow_empty: object_list = [] else: raise Http404 current_page = paginator.page(page) c = RequestContext(request, { '%s_list' % self.template_object_name: object_list, 'is_paginated': paginator.num_pages > 1, 'results_per_page': self.paginate_by, 'has_next': current_page.has_next(), 'has_previous': current_page.has_previous(), 'page': page, 'next': page + 1, 'previous': page - 1, 'last_on_page': current_page.end_index(), 'first_on_page': current_page.start_index(), 'pages': paginator.num_pages, 'hits': paginator.count, }, self.context_processors) else: object_list = queryset c = RequestContext(request, {'%s_list' \ % self.template_object_name: object_list, 'is_paginated': False}, self.context_processors) if not self.allow_empty and len(queryset) == 0: raise Http404 # Hide unexposed fields for obj in object_list: self._hide_unexposed_fields(obj, self.expose_fields) c.update(self.extra_context) t = self.template_loader.get_template(template_name) return HttpResponse(t.render(c), mimetype=self.mimetype)
def index(request, page=1): "Home page with pagination. Adapted to the trunk version of paginator" newurl = '' #cgi.parse_qsl(request) if (len(request.GET)>0) or (len(request.META['PATH_INFO']) >0): if request.GET: search_form = SearchForm(request.GET) else: values = {} for k,v in cgi.parse_qs(request.META['PATH_INFO'], True).items(): if v[0] != 'None': values[k] = v[0] search_form = SearchForm(values) if search_form.is_valid(): searchdict = search_form.cleaned_data qdict = { 'first_name': 'first_name__icontains', 'last_name': 'last_name__icontains', 'age': 'age'} q_objs = [Q(**{qdict[k]: searchdict[k]}) for k in qdict.keys() if searchdict.get(k, None)] results = Person.objects.select_related().filter(*q_objs).order_by('first_name') # Encode the GET data to a URL so we can append it to the next # and previous page links. rawurl = urllib.urlencode(searchdict) if len(rawurl): newurl = '&' + rawurl else: results = Person.objects.all() search_form = SearchForm() data = dict() actual_page = int(page) paginator = QuerySetPaginator(results,RECORDS_PER_PAGE) try: data['page'] = paginator.page(int(page)) except EmptyPage: data['page'] = paginator.page(1) actual_page = 1 data['paginator'] = paginator data['url']='/agenda/list/page/' #numbre of pages you want visible if actual_page+VISIBLE_PAGES-1 <= paginator.num_pages: start_range = actual_page else: start_range = max(paginator.num_pages - VISIBLE_PAGES +1,1) end_range= min(start_range+VISIBLE_PAGES-1, paginator.num_pages) data['range'] = range(start_range,end_range) data['end_range'] = end_range data['form'] = search_form data['newurl'] = newurl return render_to_response('agenda/index.html',data, context_instance=RequestContext(request))
def list(self, request, queryset, page=None): """ Renders a list of model objects to HttpResponse. """ template_name = '%s/%s_list.html' % (self.template_dir, queryset.model._meta.module_name) if self.paginate_by: paginator = QuerySetPaginator(queryset, self.paginate_by) if not page: page = request.GET.get('page', 1) try: page = int(page) object_list = paginator.page(page).object_list except (InvalidPage, ValueError): if page == 1 and self.allow_empty: object_list = [] else: raise Http404 current_page = paginator.page(page) c = RequestContext( request, { '%s_list' % self.template_object_name: object_list, 'is_paginated': paginator.num_pages > 1, 'results_per_page': self.paginate_by, 'has_next': current_page.has_next(), 'has_previous': current_page.has_previous(), 'page': page, 'next': page + 1, 'previous': page - 1, 'last_on_page': current_page.end_index(), 'first_on_page': current_page.start_index(), 'pages': paginator.num_pages, 'hits': paginator.count, }, self.context_processors) else: object_list = queryset c = RequestContext( request, { '%s_list' % self.template_object_name: object_list, 'is_paginated': False }, self.context_processors) if not self.allow_empty and len(queryset) == 0: raise Http404 # Hide unexposed fields for obj in object_list: self._hide_unexposed_fields(obj, self.expose_fields) c.update(self.extra_context) t = self.template_loader.get_template(template_name) return HttpResponse(t.render(c), mimetype=self.mimetype)
def paginate_queryset_for_request(request, qset, paginate_by): """ returns appropriate page for view. Page number should be set in GET variable 'p', if not set first page is returned. """ item_number_mapping = {} for i, c in enumerate(qset): item_number_mapping[c._get_pk_val()] = i + 1 paginator = QuerySetPaginator(qset, paginate_by) page_no = request.GET.get('p', paginator.page_range[0]) try: page_no = int(page_no) if not page_no in paginator.page_range: page_no = paginator.page_range[0] except Exception: page_no = paginator.page_range[0] context = {} page = paginator.page(page_no) objs = page.object_list context['object_list'] = objs context.update({ 'is_paginated': paginator.num_pages > 1, 'results_per_page': paginate_by, 'page': page, 'item_number_mapping': item_number_mapping, }) return context
def series_and_issue(request, series_name, issue_nr, sort=ORDER_ALPHA): """ Looks for issue_nr in series_name """ things = Issue.objects.filter(series__name__exact = series_name) \ .filter(number__exact = issue_nr) if things.count() == 1: # if one display the issue return HttpResponseRedirect(urlresolvers.reverse(issue, kwargs={ 'issue_id': things[0].id })) else: # if more or none use issue_list.html from search p = QuerySetPaginator(things, 100) page_num = 1 if (request.GET.has_key('page')): page_num = int(request.GET['page']) page = p.page(page_num) context = { 'items' : things, 'item_name' : 'issue', 'plural_suffix' : 's', 'heading' : series_name + ' #' + issue_nr, 'style' : 'default', } if 'style' in request.GET: context['style'] = request.GET['style'] return paginate_response( request, things, 'gcd/search/issue_list.html', context)
def detail(request, context): """ Custom object detail function that adds a QuestionForm to the context. """ interview = context['object'] page_no = get_page_no(request) qset = interview.get_questions() paginator = QuerySetPaginator(qset, interviews_settings.PAGINATION_PER_PAGE) if page_no > paginator.num_pages or page_no < 1: raise Http404 page = paginator.page(page_no) interviewees = interview.get_interviewees(request.user) context.update({ 'interviewees': interviewees, 'is_paginated': paginator.num_pages > 1, 'results_per_page': interviews_settings.PAGINATION_PER_PAGE, 'page': page, 'form': QuestionForm(request), 'questions': page.object_list, }) return render_to_response(get_templates_from_placement( 'object.html', context['placement']), context, context_instance=RequestContext(request))
def search_results(request, service_id, search_terms, page_number=1): results = DownloadLink.objects.filter(hidden=False) term_list = search_terms.split('+') for term in term_list: results = results.filter(name__icontains=term) try: service = DownloadService.objects.get(id=service_id) results = results.filter(service=service) except: service = None paginator = QuerySetPaginator(results, 25) page = paginator.page(page_number) return render_response( request, 'style2/search_results.html', '', { 'search_terms': search_terms, 'service': service, 'page': page, 'paginator': paginator, 'term_list': term_list, })
def detail(request, context): """ Custom object detail function that adds a QuestionForm to the context. """ interview = context['object'] page_no = get_page_no(request) qset = interview.get_questions() paginator = QuerySetPaginator(qset, INTERVIEW_PAGINATION_PER_PAGE) if page_no > paginator.num_pages or page_no < 1: raise Http404 page = paginator.page(page_no) interviewees = interview.get_interviewees(request.user) context.update({ 'interviewees': interviewees, 'is_paginated': paginator.num_pages > 1, 'results_per_page': INTERVIEW_PAGINATION_PER_PAGE, 'page': page, 'form' : QuestionForm(request=request), 'questions' : page.object_list, }) return render_to_response( get_templates_from_placement('object.html', context['placement']), context, context_instance=RequestContext(request) )
def list( self, request, queryset, page=None, ): """ Renders a list of model objects to HttpResponse. """ if self.paginate_by: paginator = QuerySetPaginator(queryset, self.paginate_by) if not page: page = request.GET.get('page', 1) try: page = int(page) object_list = paginator.page(page).object_list except (InvalidPage, ValueError): if page == 1 and self.allow_empty: object_list = [] else: return self.error(request, 404) else: object_list = list(queryset) return HttpResponse(self.render(object_list), self.mimetype)
def search(request): import re from pysolr import Solr from stats.models import DailySearch from settings import SOLR_URL def _fail(query): # phrase is not changed, query is normalized phrase return render_to_response('search_results.html', { 'result': [], 'query': query, 'phrase': query, }, context_instance=RequestContext(request)) phrase = request.GET.get('phrase') try: conn = Solr(SOLR_URL) except: return _fail(phrase) result = [] if not phrase: raise Http404("Malformed request.") q = phrase if phrase.startswith('*') or phrase.startswith('?'): q = phrase[1:] q = q.strip() q = re.sub('[' + '\[<>@\]' + ']', '', q) q = re.sub('`', '"', q) q = re.sub('\s*:', ':', q) q = re.sub( '(?<!author)(?<!title)(?<!text)(?<!file)(?<!tag)(?<!artist)(?<!album)(?<!year)(?<!company)(?<!created):', ' ', q) if not q: return _fail(phrase) results = conn.search(q) if not results: return _fail(q) ids = [i['id'] for i in results] result = QuerySetPaginator(Topic.objects.filter(pk__in=ids), RESULTS_ON_PAGE, orphans=5) if result.num_pages == 0: return _fail(q) p = DailySearch.objects.create(phrase=q.strip()) page = request.GET.get('page', 1) try: page = int(page) r = result.page(page) except (InvalidPage, ValueError): raise Http404("No such page") return render_to_response('search_results.html', { 'result': r, 'query': q, 'phrase': phrase, 'page': int(page), 'title': phrase, }, context_instance=RequestContext(request))
def user_list(request): queryset = Timeline.objects.filter(user__is_active=True).values("user").annotate(last=Max("time")).order_by("-last") if request.current_user: ctype = ContentType.objects.get_for_model(User) favorites = Favorite.objects.filter(user=request.current_user, content_type=ctype).values_list( "object_id", flat=True ) queryset = queryset.filter(user__id__in=favorites) paginator = QuerySetPaginator(queryset, 20) try: page = paginator.page(request.GET.get("p", 1)) except (EmptyPage, InvalidPage): page = paginator.page(paginator.num_pages) return render_to_response( "timeline/user_list.html", {"user_list": [User.objects.get(id=item["user"]) for item in page.object_list], "page": page}, context_instance=RequestContext(request), )
def make_pages(querySet, items_at_page=20, current_page=None): pages = QuerySetPaginator(querySet, items_at_page) page_number = validate_page_number(current_page, pages.num_pages) posts = pages.page(page_number).object_list context = {'items': posts} context.update(other_pages(page_number, pages.num_pages)) return context
def browse2(request, page_number=1): links = DownloadLink.objects.filter(hidden=False) paginator = QuerySetPaginator(links, 25) page = paginator.page(page_number) return render_response(request, 'style2/link_browse_json.html', '', { 'page': page, 'paginator': paginator, })
def surveygroup_list(request): page_no = int(request.GET.get('page', 1)) surveygroup_list = SurveyGroup.objects.all() p = QuerySetPaginator(surveygroup_list, 50) page = p.page(page_no) return render_to_response('django_surveys/surveygroup_list.html', locals(), context_instance=RequestContext(request))
def survey_list(request, surveygroup_id): surveygroup = get_object_or_404(SurveyGroup, pk=surveygroup_id) survey_list = Survey.objects.filter(survey_group__id=surveygroup_id) page_no = int(request.GET.get('page', 1)) p = QuerySetPaginator(survey_list, 50) page = p.page(page_no) return render_to_response('django_surveys/survey_list.html', locals(), context_instance=RequestContext(request))
def link_topic(request, topic_id, page_number=1): topic = get_object_or_404(DownloadTopics, pk=topic_id) links = DownloadLink.objects.filter(hidden=False, from_topic=topic) paginator = QuerySetPaginator(links, 26) page = paginator.page(page_number) return render_response(request, 'style2/link_topic.html', '', { 'topic': topic, 'page': page, 'paginator': paginator, })
def search(request): import re from pysolr import Solr from stats.models import DailySearch from settings import SOLR_URL def _fail(query): # phrase is not changed, query is normalized phrase return render_to_response('search_results.html', { 'result': [], 'query': query, 'phrase': query, }, context_instance=RequestContext(request)) phrase = request.GET.get('phrase') try: conn = Solr(SOLR_URL) except: return _fail(phrase) result = [] if not phrase: raise Http404("Malformed request.") q = phrase if phrase.startswith('*') or phrase.startswith('?'): q = phrase[1:] q = q.strip() q = re.sub('['+'\[<>@\]'+']', '', q) q = re.sub('`', '"', q) q = re.sub('\s*:',':', q) q = re.sub('(?<!author)(?<!title)(?<!text)(?<!file)(?<!tag)(?<!artist)(?<!album)(?<!year)(?<!company)(?<!created):', ' ', q) if not q: return _fail(phrase) results = conn.search(q) if not results: return _fail(q) ids = [i['id'] for i in results] result = QuerySetPaginator(Topic.objects.filter(pk__in=ids), RESULTS_ON_PAGE, orphans=5) if result.num_pages == 0: return _fail(q) p = DailySearch.objects.create(phrase=q.strip()) page = request.GET.get('page', 1) try: page = int(page) r = result.page(page) except (InvalidPage, ValueError): raise Http404("No such page") return render_to_response('search_results.html', { 'result': r, 'query': q, 'phrase': phrase, 'page': int(page), 'title': phrase, }, context_instance=RequestContext(request))
def service_listing(request, service_id, page_number=1): service = get_object_or_404(DownloadService, pk=service_id) links = service.downloadlink_set.filter(hidden=False) paginator = QuerySetPaginator(links, 25) page = paginator.page(page_number) return render_response(request, 'style2/service_view.html', '/service/%s/' % service_id, { 'service': service, 'page': page, 'paginator': paginator, })
def list(self, request, queryset, page=None): """ Renders a list of model objects to HttpResponse. """ if self.paginate_by: paginator = QuerySetPaginator(queryset, self.paginate_by) if not page: page = request.GET.get('page', 1) try: page = int(page) object_list = paginator.page(page).object_list except (InvalidPage, ValueError): if page == 1 and self.allow_empty: object_list = [] else: return self.error(request, 404) else: object_list = list(queryset) return HttpResponse(self.render(object_list), self.mimetype)
def get_results(self, request): paginator = QuerySetPaginator(self.query_set, self.lookup_opts.admin.list_per_page) # Get the number of objects, with admin filters applied. try: result_count = paginator.count # Naked except! Because we don't have any other way of validating # "params". They might be invalid if the keyword arguments are # incorrect, or if the values are not in the correct type (which would # result in a database error). except: raise IncorrectLookupParameters # Get the total number of objects, with no admin filters applied. # Perform a slight optimization: Check to see whether any filters were # given. If not, use paginator.hits to calculate the number of objects, # because we've already done paginator.hits and the value is cached. if isinstance(self.query_set._filters, models.Q) and not self.query_set._filters.kwargs: full_result_count = result_count else: full_result_count = self.manager.count() can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED multi_page = result_count > self.lookup_opts.admin.list_per_page # Get the list of objects to display on this page. if (self.show_all and can_show_all) or not multi_page: result_list = list(self.query_set) else: try: result_list = paginator.page(self.page_num + 1).object_list except InvalidPage: result_list = () self.result_count = result_count self.full_result_count = full_result_count self.result_list = result_list self.can_show_all = can_show_all self.multi_page = multi_page self.paginator = paginator
def post_list(request): page_no = request.GET.get('page', 1) if page_no == 1: cached_page = cache.get('/') if cached_page: return cached_page paginator = QuerySetPaginator(Post.objects.all(), 10) context = { 'paginator': paginator, 'page': paginator.page(page_no), } response = render_to_response('blog/post_list.html', context, RequestContext(request)) if page_no == 1: cache.set('/', response) return response
def render(request, template, dict={}): r = Result.objects.all().order_by('-run_date', '-upload_date') p = QuerySetPaginator(r, 50) try: page_num = int(request.GET['p']) if page_num < 1: page_num = 1 elif page_num > p.num_pages: page_num = p.num_pages except: page_num = 1 page = p.page(page_num) dict['p'] = p dict['page'] = page dict['results'] = result_list(page.object_list) dict['r'] = result_list(r) return render_to_response(template, dict)
def get_results(self, request): paginator = QuerySetPaginator(self.query_set, self.lookup_opts.admin.list_per_page) # Get the number of objects, with admin filters applied. try: result_count = paginator.count # Naked except! Because we don't have any other way of validating # "params". They might be invalid if the keyword arguments are # incorrect, or if the values are not in the correct type (which would # result in a database error). except: raise IncorrectLookupParameters # Get the total number of objects, with no admin filters applied. # Perform a slight optimization: Check to see whether any filters were # given. If not, use paginator.hits to calculate the number of objects, # because we've already done paginator.hits and the value is cached. if isinstance(self.query_set._filters, models.Q) and not self.query_set._filters.kwargs: full_result_count = result_count else: full_result_count = self.manager.count() can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED multi_page = result_count > self.lookup_opts.admin.list_per_page # Get the list of objects to display on this page. if (self.show_all and can_show_all) or not multi_page: result_list = list(self.query_set) else: try: result_list = paginator.page(self.page_num+1).object_list except InvalidPage: result_list = () self.result_count = result_count self.full_result_count = full_result_count self.result_list = result_list self.can_show_all = can_show_all self.multi_page = multi_page self.paginator = paginator
def list(request, page=0): comments = FreeComment.objects.filter(content_type=Photo, approved=True, is_public=True) paginator = QuerySetPaginator(comments, 25, allow_empty_first_page=True) if not page: page = request.GET.get("page", 1) try: page_number = int(page) except ValueError: if page == "last": page_number = paginator.num_pages else: raise Http404 try: page_obj = paginator.page(page_number) except InvalidPage: raise Http404 return render( request=request, template_name="photos/comment_list.html", payload={ "comments": comments, "is_paginated": page_obj.has_other_pages(), "results_per_page": paginator.per_page, "has_next": page_obj.has_next(), "has_previous": page_obj.has_previous(), "page": page_obj.number, "next": page_obj.next_page_number(), "previous": page_obj.previous_page_number(), "first_on_page": page_obj.start_index(), "last_on_page": page_obj.end_index(), "pages": paginator.num_pages, "hits": paginator.count, "page_range": paginator.page_range, }, )
def list(request, page=0): comments = FreeComment.objects.filter(content_type=Photo, approved=True, is_public=True) paginator = QuerySetPaginator(comments, 25, allow_empty_first_page=True) if not page: page = request.GET.get('page', 1) try: page_number = int(page) except ValueError: if page == 'last': page_number = paginator.num_pages else: raise Http404 try: page_obj = paginator.page(page_number) except InvalidPage: raise Http404 return render(request=request, template_name='photos/comment_list.html', payload={ 'comments': comments, 'is_paginated': page_obj.has_other_pages(), 'results_per_page': paginator.per_page, 'has_next': page_obj.has_next(), 'has_previous': page_obj.has_previous(), 'page': page_obj.number, 'next': page_obj.next_page_number(), 'previous': page_obj.previous_page_number(), 'first_on_page': page_obj.start_index(), 'last_on_page': page_obj.end_index(), 'pages': paginator.num_pages, 'hits': paginator.count, 'page_range': paginator.page_range, })
def detail(request, gallery_slug, page=0): gallery = get_object_or_404(Gallery, slug__iexact=gallery_slug) photos = Photo.objects.filter(gallery=gallery) paginator = QuerySetPaginator(photos, 25, allow_empty_first_page=True) if not page: page = request.GET.get('page', 1) try: page_number = int(page) except ValueError: if page == 'last': page_number = paginator.num_pages else: raise Http404 try: page_obj = paginator.page(page_number) except InvalidPage: raise Http404 return render(request, 'photos/gallery_detail.html', { 'gallery': gallery, 'photos': photos, 'is_paginated': page_obj.has_other_pages(), 'results_per_page': paginator.per_page, 'has_next': page_obj.has_next(), 'has_previous': page_obj.has_previous(), 'page': page_obj.number, 'next': page_obj.next_page_number(), 'previous': page_obj.previous_page_number(), 'first_on_page': page_obj.start_index(), 'last_on_page': page_obj.end_index(), 'pages': paginator.num_pages, 'hits': paginator.count, 'page_range': paginator.page_range, })
def search_results(request, service_id, search_terms, page_number=1): results = DownloadLink.objects.filter(hidden=False) term_list = search_terms.split('+') for term in term_list: results = results.filter(name__icontains=term) try: service = DownloadService.objects.get(id=service_id) results = results.filter(service=service) except: service = None paginator = QuerySetPaginator(results, 25) page = paginator.page(page_number) return render_response(request, 'style2/search_results.html', '', { 'search_terms': search_terms, 'service': service, 'page': page, 'paginator': paginator, 'term_list': term_list, })
def archive_index(request, queryset, date_field, num_latest=15, page=None, template_name=None, template_loader=loader, extra_context=None, allow_empty=True, context_processors=None, mimetype=None, allow_future=False, template_object_name='latest'): """ Generic top-level archive of date-based objects. A replacement for ``django.views.generic.date_based.archive_index`` which offers result-set pagination. Templates: ``<app_label>/<model_name>_archive.html`` Context: date_list List of years latest Latest N (defaults to 15) objects by date paginator ``django.core.paginator.Paginator`` object (if ``num_latest`` provided) page_obj ``django.core.paginator.Page`` object (if ``num_latest`` provided) """ if extra_context is None: extra_context = {} model = queryset.model if not allow_future: queryset = queryset.filter(**{'%s__lte' % date_field: datetime.datetime.now()}) date_list = queryset.dates(date_field, 'year')[::-1] if not date_list and not allow_empty: raise Http404, "No %s available" % model._meta.verbose_name if date_list and num_latest: latest = queryset.order_by('-'+date_field) paginator = QuerySetPaginator(latest, per_page=num_latest, allow_empty_first_page=allow_empty) if not page: page = request.GET.get('page', 1) try: page_number = int(page) except ValueError: if page == 'last': page_number = paginator.num_pages else: raise Http404 try: page_obj = paginator.page(page_number) except InvalidPage: raise Http404 c = RequestContext(request, { 'date_list': date_list, template_object_name: page_obj.object_list, 'paginator': paginator, 'page_obj': page_obj, }, context_processors) else: latest = None c = RequestContext(request, { 'date_list' : date_list, template_object_name : latest, 'paginator': None, 'page_obj': None, }, context_processors) if not template_name: template_name = "%s/%s_archive.html" % (model._meta.app_label, model._meta.object_name.lower()) t = template_loader.get_template(template_name) for key, value in extra_context.items(): if callable(value): c[key] = value() else: c[key] = value return HttpResponse(t.render(c), mimetype=mimetype)
class DataGrid(object): """ A representation of a list of objects, sorted and organized by columns. The sort order and column lists can be customized. allowing users to view this data however they prefer. This is meant to be subclassed for specific uses. The subclasses are responsible for defining one or more column types. It can also set one or more of the following optional variables: * 'title': The title of the grid. * 'profile_sort_field': The variable name in the user profile where the sort order can be loaded and saved. * 'profile_columns_field": The variable name in the user profile where the columns list can be loaded and saved. * 'paginate_by': The number of items to show on each page of the grid. The default is 50. * 'paginate_orphans': If this number of objects or fewer are on the last page, it will be rolled into the previous page. The default is 3. * 'page': The page to display. If this is not specified, the 'page' variable passed in the URL will be used, or 1 if that is not specified. * 'listview_template': The template used to render the list view. The default is 'datagrid/listview.html' * 'column_header_template': The template used to render each column header. The default is 'datagrid/column_header.html' * 'cell_template': The template used to render a cell of data. The default is 'datagrid/cell.html' * 'optimize_sorts': Whether or not to optimize queries when using multiple sorts. This can offer a speed improvement, but may need to be turned off for more advanced querysets (such as when using extra()). The default is True. """ def __init__(self, request, queryset=None, title="", extra_context={}, optimize_sorts=True): self.request = request self.queryset = queryset self.rows = [] self.columns = [] self.all_columns = [] self.db_field_map = {} self.id_list = [] self.paginator = None self.page = None self.sort_list = None self.state_loaded = False self.page_num = 0 self.id = None self.extra_context = dict(extra_context) self.optimize_sorts = optimize_sorts self.cell_template_obj = None self.column_header_template_obj = None if not hasattr(request, "datagrid_count"): request.datagrid_count = 0 self.id = "datagrid-%s" % request.datagrid_count request.datagrid_count += 1 # Customizable variables self.title = title self.profile_sort_field = None self.profile_columns_field = None self.paginate_by = 50 self.paginate_orphans = 3 self.listview_template = 'datagrid/listview.html' self.column_header_template = 'datagrid/column_header.html' self.cell_template = 'datagrid/cell.html' for attr in dir(self): column = getattr(self, attr) if isinstance(column, Column): self.all_columns.append(column) column.datagrid = self column.id = attr # Reset the column. column.reset() if not column.field_name: column.field_name = column.id if not column.db_field: column.db_field = column.field_name self.db_field_map[column.id] = column.db_field self.all_columns.sort(key=lambda x: x.label) def load_state(self, render_context=None): """ Loads the state of the datagrid. This will retrieve the user-specified or previously stored sorting order and columns list, as well as any state a subclass may need. """ if self.state_loaded: return profile_sort_list = None profile_columns_list = None profile = None profile_dirty = False # Get the saved settings for this grid in the profile. These will # work as defaults and allow us to determine if we need to save # the profile. if self.request.user.is_authenticated(): try: profile = self.request.user.get_profile() if self.profile_sort_field: profile_sort_list = \ getattr(profile, self.profile_sort_field, None) if self.profile_columns_field: profile_columns_list = \ getattr(profile, self.profile_columns_field, None) except SiteProfileNotAvailable: pass except ObjectDoesNotExist: pass # Figure out the columns we're going to display # We're also going to calculate the column widths based on the # shrink and expand values. colnames_str = self.request.GET.get('columns', profile_columns_list) if colnames_str: colnames = colnames_str.split(',') else: colnames = self.default_columns colnames_str = ",".join(colnames) expand_columns = [] normal_columns = [] for colname in colnames: try: column = getattr(self, colname) except AttributeError: # The user specified a column that doesn't exist. Skip it. continue self.columns.append(column) column.active = True if column.expand: # This column is requesting all remaining space. Save it for # later so we can tell how much to give it. Each expanded # column will count as two normal columns when calculating # the normal sized columns. expand_columns.append(column) elif column.shrink: # Make this as small as possible. column.width = 0 else: # We'll divide the column widths equally after we've built # up the lists of expanded and normal sized columns. normal_columns.append(column) self.columns[-1].last = True # Try to figure out the column widths for each column. # We'll start with the normal sized columns. total_pct = 100 # Each expanded column counts as two normal columns. normal_column_width = total_pct / (len(self.columns) + len(expand_columns)) for column in normal_columns: column.width = normal_column_width total_pct -= normal_column_width if len(expand_columns) > 0: expanded_column_width = total_pct / len(expand_columns) else: expanded_column_width = 0 for column in expand_columns: column.width = expanded_column_width # Now get the sorting order for the columns. sort_str = self.request.GET.get('sort', profile_sort_list) if sort_str: self.sort_list = sort_str.split(',') else: self.sort_list = self.default_sort sort_str = ",".join(self.sort_list) # A subclass might have some work to do for loading and saving # as well. if self.load_extra_state(profile): profile_dirty = True # Now that we have all that, figure out if we need to save new # settings back to the profile. if profile: if self.profile_columns_field and \ colnames_str != profile_columns_list: setattr(profile, self.profile_columns_field, colnames_str) profile_dirty = True if self.profile_sort_field and sort_str != profile_sort_list: setattr(profile, self.profile_sort_field, sort_str) profile_dirty = True if profile_dirty: profile.save() self.state_loaded = True # Fetch the list of objects and have it ready. self.precompute_objects(render_context) def load_extra_state(self, profile): """ Loads any extra state needed for this grid. This is used by subclasses that may have additional data to load and save. This should return True if any profile-stored state has changed, or False otherwise. """ return False def precompute_objects(self, render_context=None): """ Builds the queryset and stores the list of objects for use in rendering the datagrid. """ query = self.queryset use_select_related = False # Generate the actual list of fields we'll be sorting by sort_list = [] for sort_item in self.sort_list: if sort_item[0] == "-": base_sort_item = sort_item[1:] prefix = "-" else: base_sort_item = sort_item prefix = "" if sort_item and base_sort_item in self.db_field_map: db_field = self.db_field_map[base_sort_item] sort_list.append(prefix + db_field) # Lookups spanning tables require that we query from those # tables. In order to keep things simple, we'll just use # select_related so that we don't have to figure out the # table relationships. We only do this if we have a lookup # spanning tables. if '.' in db_field: use_select_related = True if sort_list: query = query.order_by(*sort_list) self.paginator = QuerySetPaginator(query.distinct(), self.paginate_by, self.paginate_orphans) page_num = self.request.GET.get('page', 1) # Accept either "last" or a valid page number. if page_num == "last": page_num = self.paginator.num_pages try: self.page = self.paginator.page(page_num) except InvalidPage: raise Http404 self.id_list = [] if self.optimize_sorts and len(sort_list) > 0: # This can be slow when sorting by multiple columns. If we # have multiple items in the sort list, we'll request just the # IDs and then fetch the actual details from that. self.id_list = list(self.page.object_list.values_list( 'pk', flat=True)) # Make sure to unset the order. We can't meaningfully order these # results in the query, as what we really want is to keep it in # the order specified in id_list, and we certainly don't want # the database to do any special ordering (possibly slowing things # down). We'll set the order properly in a minute. self.page.object_list = self.post_process_queryset( self.queryset.model.objects.filter( pk__in=self.id_list).order_by()) if use_select_related: self.page.object_list = \ self.page.object_list.select_related(depth=1) if self.id_list: # The database will give us the items in a more or less random # order, since it doesn't know to keep it in the order provided by # the ID list. This will place the results back in the order we # expect. index = dict([(id, pos) for (pos, id) in enumerate(self.id_list)]) object_list = [None] * len(self.id_list) for obj in list(self.page.object_list): object_list[index[obj.pk]] = obj else: # Grab the whole list at once. We know it won't be too large, # and it will prevent one query per row. object_list = list(self.page.object_list) for column in self.columns: column.collect_objects(object_list) if render_context is None: render_context = self._build_render_context() self.rows = [ { 'object': obj, 'cells': [column.render_cell(obj, render_context) for column in self.columns] } for obj in object_list if obj is not None ] def post_process_queryset(self, queryset): """ Processes a QuerySet after the initial query has been built and pagination applied. This is only used when optimizing a sort. By default, this just returns the existing queryset. Custom datagrid subclasses can override this to add additional queries (such as subqueries in an extra() call) for use in the cell renderers. When optimize_sorts is True, subqueries (using extra()) on the initial QuerySet passed to the datagrid will be stripped from the final result. This function can be used to re-add those subqueries. """ for column in self.columns: queryset = column.augment_queryset(queryset) return queryset def render_listview(self, render_context=None): """ Renders the standard list view of the grid. This can be called from templates. """ try: if render_context is None: render_context = self._build_render_context() self.load_state(render_context) context = Context({ 'datagrid': self, 'is_paginated': self.page.has_other_pages(), 'results_per_page': self.paginate_by, 'has_next': self.page.has_next(), 'has_previous': self.page.has_previous(), 'page': self.page.number, 'next': self.page.next_page_number(), 'previous': self.page.previous_page_number(), 'last_on_page': self.page.end_index(), 'first_on_page': self.page.start_index(), 'pages': self.paginator.num_pages, 'hits': self.paginator.count, 'page_range': self.paginator.page_range, }) context.update(self.extra_context) context.update(render_context) return mark_safe(render_to_string(self.listview_template, context)) except Exception: trace = traceback.format_exc(); logging.error('Failed to render datagrid:\n%s' % trace, extra={ 'request': self.request, }) return mark_safe('<pre>%s</pre>' % trace) def render_listview_to_response(self, request=None, render_context=None): """ Renders the listview to a response, preventing caching in the process. """ response = HttpResponse(unicode(self.render_listview(render_context))) patch_cache_control(response, no_cache=True, no_store=True, max_age=0, must_revalidate=True) return response def render_to_response(self, template_name, extra_context={}): """ Renders a template containing this datagrid as a context variable. """ render_context = self._build_render_context() self.load_state(render_context) # If the caller is requesting just this particular grid, return it. if self.request.GET.get('gridonly', False) and \ self.request.GET.get('datagrid-id', None) == self.id: return self.render_listview_to_response( render_context=render_context) context = Context({ 'datagrid': self }) context.update(extra_context) context.update(render_context) return render_to_response(template_name, context) def _build_render_context(self): """Builds a dictionary containing RequestContext contents. A RequestContext can be expensive, so it's best to reuse the contents of one when possible. This is not easy with a standard RequestContext, but it's possible to build one and then pull out the contents into a dictionary. """ request_context = RequestContext(self.request) render_context = {} for d in request_context: render_context.update(d) return render_context @staticmethod def link_to_object(obj, value): return obj.get_absolute_url() @staticmethod def link_to_value(obj, value): return value.get_absolute_url()
def multi_object_list(request, querysets, template_name=None, template_loader=loader, extra_context=None, context_processors=None, mimetype=None): """ Generic list of objects. Querysets is a sequence of tuples, where the first element of each tuple is the queryset, and the second element is a dictionary specifying the parameters for that queryset. Unused parameters are set to the following default values. Defaults: name: required, throws error if not present paginate_by: don't paginate page: get page from HTTP request allow_emptys: None Templates: ``<app_label>/<model_name>_[<model_name>_]*list.html`` Context: object_contexts dict mapping queryname to a dict containing the following keys: queryname_list list of objects is_paginated are the results paginated? results_per_page number of objects per page (if paginated) has_next is there a next page? has_previous is there a prev page? page the current page next the next page previous the previous page pages number of pages, total hits number of objects, total last_on_page the result number of the last of object in the object_list (1-indexed) first_on_page the result number of the first object in the object_list (1-indexed) page_range: A list of the page numbers (1-indexed). """ if extra_context is None: extra_context = {} object_contexts = {} for querytuple in querysets: queryset, parameters = querytuple queryset = queryset._clone() paginate_by = parameters.get('paginate_by', None) allow_empty = parameters.get('allow_empty', None) if paginate_by: paginator = QuerySetPaginator(queryset, paginate_by, allow_empty_first_page=allow_empty) page = parameters.get('page', None) if not page: page = request.GET.get((parameters['name'] + '_page'), 1) try: page_number = int(page) except ValueError: if page == 'last': page_number = paginator.num_pages else: # Page is not 'last', nor can it be converted to an int. raise Http404 try: page_obj = paginator.page(page_number) except InvalidPage: raise Http404 object_context = { '%s_list' % parameters['name']: page_obj.object_list, 'paginator': paginator, 'page_obj': page_obj, # Legacy template context stuff. New templates should use page_obj # to access this instead. 'is_paginated': page_obj.has_other_pages(), 'results_per_page': paginator.per_page, 'has_next': page_obj.has_next(), 'has_previous': page_obj.has_previous(), 'page': page_obj.number, 'next': page_obj.next_page_number(), 'previous': page_obj.previous_page_number(), 'first_on_page': page_obj.start_index(), 'last_on_page': page_obj.end_index(), 'pages': paginator.num_pages, 'hits': paginator.count, 'page_range': paginator.page_range, } else: object_context = { '%s_list' % parameters['name']: queryset, 'paginator': None, 'page_obj': None, 'is_paginated': False, } if not allow_empty and len(queryset) == 0: raise Http404 object_contexts[parameters['name']] = object_context c = RequestContext(request, object_contexts , context_processors) for key, value in extra_context.items(): if callable(value): c[key] = value() else: c[key] = value if not template_name: object_names = [] app_label = '' for (queryset, parameters) in querysets: model = queryset.model app_label = model._meta.app_label object_names.append(model._meta.object_name.lower()) object_name = '_'.join(object_names) template_name = "%s/%slist.html" % (app_label, object_name) t = template_loader.get_template(template_name) return HttpResponse(t.render(c), mimetype=mimetype)
class DataGrid(object): """ A representation of a list of objects, sorted and organized by columns. The sort order and column lists can be customized. allowing users to view this data however they prefer. This is meant to be subclassed for specific uses. The subclasses are responsible for defining one or more column types. It can also set one or more of the following optional variables: * 'title': The title of the grid. * 'profile_sort_field': The variable name in the user profile where the sort order can be loaded and saved. * 'profile_columns_field": The variable name in the user profile where the columns list can be loaded and saved. * 'paginate_by': The number of items to show on each page of the grid. The default is 50. * 'paginate_orphans': If this number of objects or fewer are on the last page, it will be rolled into the previous page. The default is 3. * 'page': The page to display. If this is not specified, the 'page' variable passed in the URL will be used, or 1 if that is not specified. * 'listview_template': The template used to render the list view. The default is 'datagrid/listview.html' * 'column_header_template': The template used to render each column header. The default is 'datagrid/column_header.html' * 'cell_template': The template used to render a cell of data. The default is 'datagrid/cell.html' """ def __init__(self, request, queryset=None, title="", extra_context={}): self.request = request self.queryset = queryset self.rows = [] self.columns = [] self.all_columns = [] self.db_field_map = {} self.paginator = None self.page = None self.sort_list = None self.state_loaded = False self.page_num = 0 self.id = None self.extra_context = dict(extra_context) if not hasattr(request, "datagrid_count"): request.datagrid_count = 0 self.id = "datagrid-%s" % request.datagrid_count request.datagrid_count += 1 # Customizable variables self.title = title self.profile_sort_field = None self.profile_columns_field = None self.paginate_by = 50 self.paginate_orphans = 3 self.listview_template = 'datagrid/listview.html' self.column_header_template = 'datagrid/column_header.html' self.cell_template = 'datagrid/cell.html' for attr in dir(self): column = getattr(self, attr) if isinstance(column, Column): self.all_columns.append(column) column.datagrid = self column.id = attr # Reset the column. column.active = False column.last = False column.width = 0 if not column.field_name: column.field_name = column.id if not column.db_field: column.db_field = column.field_name self.db_field_map[column.id] = column.db_field self.all_columns.sort(key=lambda x: x.label) def load_state(self): """ Loads the state of the datagrid. This will retrieve the user-specified or previously stored sorting order and columns list, as well as any state a subclass may need. """ if self.state_loaded: return profile_sort_list = None profile_columns_list = None profile = None profile_dirty = False # Get the saved settings for this grid in the profile. These will # work as defaults and allow us to determine if we need to save # the profile. if self.request.user.is_authenticated(): try: profile = self.request.user.get_profile() if self.profile_sort_field: profile_sort_list = \ getattr(profile, self.profile_sort_field, None) if self.profile_columns_field: profile_columns_list = \ getattr(profile, self.profile_columns_field, None) except SiteProfileNotAvailable: pass except ObjectDoesNotExist: pass # Figure out the columns we're going to display # We're also going to calculate the column widths based on the # shrink and expand values. colnames_str = self.request.GET.get('columns', profile_columns_list) if colnames_str: colnames = colnames_str.split(',') else: colnames = self.default_columns colnames_str = ",".join(colnames) expand_columns = [] normal_columns = [] for colname in colnames: try: column = getattr(self, colname) except AttributeError: # The user specified a column that doesn't exist. Skip it. continue self.columns.append(column) column.active = True if column.expand: # This column is requesting all remaining space. Save it for # later so we can tell how much to give it. Each expanded # column will count as two normal columns when calculating # the normal sized columns. expand_columns.append(column) elif column.shrink: # Make this as small as possible. column.width = 0 else: # We'll divide the column widths equally after we've built # up the lists of expanded and normal sized columns. normal_columns.append(column) self.columns[-1].last = True # Try to figure out the column widths for each column. # We'll start with the normal sized columns. total_pct = 100 # Each expanded column counts as two normal columns. normal_column_width = total_pct / (len(self.columns) + len(expand_columns)) for column in normal_columns: column.width = normal_column_width total_pct -= normal_column_width if len(expand_columns) > 0: expanded_column_width = total_pct / len(expand_columns) else: expanded_column_width = 0 for column in expand_columns: column.width = expanded_column_width # Now get the sorting order for the columns. sort_str = self.request.GET.get('sort', profile_sort_list) if sort_str: self.sort_list = sort_str.split(',') else: self.sort_list = self.default_sort sort_str = ",".join(self.sort_list) # A subclass might have some work to do for loading and saving # as well. if self.load_extra_state(profile): profile_dirty = True # Now that we have all that, figure out if we need to save new # settings back to the profile. if profile: if self.profile_columns_field and \ colnames_str != profile_columns_list: setattr(profile, self.profile_columns_field, colnames_str) profile_dirty = True if self.profile_sort_field and sort_str != profile_sort_list: setattr(profile, self.profile_sort_field, sort_str) profile_dirty = True if profile_dirty: profile.save() self.state_loaded = True # Fetch the list of objects and have it ready. self.precompute_objects() def load_extra_state(self, profile): """ Loads any extra state needed for this grid. This is used by subclasses that may have additional data to load and save. This should return True if any profile-stored state has changed, or False otherwise. """ return False def precompute_objects(self): """ Builds the queryset and stores the list of objects for use in rendering the datagrid. """ query = self.queryset # Generate the actual list of fields we'll be sorting by sort_list = [] for sort_item in self.sort_list: if sort_item[0] == "-": base_sort_item = sort_item[1:] prefix = "-" else: base_sort_item = sort_item prefix = "" if sort_item and base_sort_item in self.db_field_map: db_field = self.db_field_map[base_sort_item] sort_list.append(prefix + db_field) if sort_list: query = query.order_by(*sort_list) # Get some of the objects we're likely to look up, in order to # reduce future queries. query = query.select_related(depth=1) self.paginator = QuerySetPaginator(query, self.paginate_by, self.paginate_orphans) page_num = self.request.GET.get('page', 1) # Accept either "last" or a valid page number. if page_num == "last": page_num = self.paginator.num_pages try: self.page = self.paginator.page(page_num) except InvalidPage: raise Http404 self.rows = [] for obj in self.page.object_list: self.rows.append({ 'object': obj, 'cells': [column.render_cell(obj) for column in self.columns] }) def render_listview(self): """ Renders the standard list view of the grid. This can be called from templates. """ self.load_state() context = { 'datagrid': self, 'is_paginated': self.page.has_other_pages(), 'results_per_page': self.paginate_by, 'has_next': self.page.has_next(), 'has_previous': self.page.has_previous(), 'page': self.page.number, 'next': self.page.next_page_number(), 'previous': self.page.previous_page_number(), 'last_on_page': self.page.end_index(), 'first_on_page': self.page.start_index(), 'pages': self.paginator.num_pages, 'hits': self.paginator.count, 'page_range': self.paginator.page_range, } context.update(self.extra_context) return mark_safe(render_to_string(self.listview_template, RequestContext(self.request, context))) @cache_control(no_cache=True, no_store=True, max_age=0, must_revalidate=True) def render_listview_to_response(self): """ Renders the listview to a response, preventing caching in the process. """ return HttpResponse(unicode(self.render_listview())) def render_to_response(self, template_name, extra_context={}): """ Renders a template containing this datagrid as a context variable. """ self.load_state() # If the caller is requesting just this particular grid, return it. if self.request.GET.get('gridonly', False) and \ self.request.GET.get('datagrid-id', None) == self.id: return self.render_listview_to_response() context = { 'datagrid': self } context.update(extra_context) context.update(self.extra_context) return render_to_response(template_name, RequestContext(self.request, context)) @staticmethod def link_to_object(obj, value): return obj.get_absolute_url() @staticmethod def link_to_value(obj, value): return value.get_absolute_url()
class DataGrid(object): """ A representation of a list of objects, sorted and organized by columns. The sort order and column lists can be customized. allowing users to view this data however they prefer. This is meant to be subclassed for specific uses. The subclasses are responsible for defining one or more column types. It can also set one or more of the following optional variables: * 'title': The title of the grid. * 'profile_sort_field': The variable name in the user profile where the sort order can be loaded and saved. * 'profile_columns_field": The variable name in the user profile where the columns list can be loaded and saved. * 'paginate_by': The number of items to show on each page of the grid. The default is 50. * 'paginate_orphans': If this number of objects or fewer are on the last page, it will be rolled into the previous page. The default is 3. * 'page': The page to display. If this is not specified, the 'page' variable passed in the URL will be used, or 1 if that is not specified. * 'listview_template': The template used to render the list view. The default is 'datagrid/listview.html' * 'column_header_template': The template used to render each column header. The default is 'datagrid/column_header.html' * 'cell_template': The template used to render a cell of data. The default is 'datagrid/cell.html' * 'optimize_sorts': Whether or not to optimize queries when using multiple sorts. This can offer a speed improvement, but may need to be turned off for more advanced querysets (such as when using extra()). The default is True. """ def __init__(self, request, queryset=None, title="", extra_context={}, optimize_sorts=True): self.request = request self.queryset = queryset self.rows = [] self.columns = [] self.all_columns = [] self.db_field_map = {} self.id_list = [] self.paginator = None self.page = None self.sort_list = None self.state_loaded = False self.page_num = 0 self.id = None self.extra_context = dict(extra_context) self.optimize_sorts = optimize_sorts self.cell_template_obj = None self.column_header_template_obj = None if not hasattr(request, "datagrid_count"): request.datagrid_count = 0 self.id = "datagrid-%s" % request.datagrid_count request.datagrid_count += 1 # Customizable variables self.title = title self.profile_sort_field = None self.profile_columns_field = None self.paginate_by = 50 self.paginate_orphans = 3 self.listview_template = 'datagrid/listview.html' self.column_header_template = 'datagrid/column_header.html' self.cell_template = 'datagrid/cell.html' for attr in dir(self): column = getattr(self, attr) if isinstance(column, Column): self.all_columns.append(column) column.datagrid = self column.id = attr # Reset the column. column.reset() if not column.field_name: column.field_name = column.id if not column.db_field: column.db_field = column.field_name self.db_field_map[column.id] = column.db_field self.all_columns.sort(key=lambda x: x.label) def load_state(self): """ Loads the state of the datagrid. This will retrieve the user-specified or previously stored sorting order and columns list, as well as any state a subclass may need. """ if self.state_loaded: return profile_sort_list = None profile_columns_list = None profile = None profile_dirty = False # Get the saved settings for this grid in the profile. These will # work as defaults and allow us to determine if we need to save # the profile. if self.request.user.is_authenticated(): try: profile = self.request.user.get_profile() if self.profile_sort_field: profile_sort_list = \ getattr(profile, self.profile_sort_field, None) if self.profile_columns_field: profile_columns_list = \ getattr(profile, self.profile_columns_field, None) except SiteProfileNotAvailable: pass except ObjectDoesNotExist: pass # Figure out the columns we're going to display # We're also going to calculate the column widths based on the # shrink and expand values. colnames_str = self.request.GET.get('columns', profile_columns_list) if colnames_str: colnames = colnames_str.split(',') else: colnames = self.default_columns colnames_str = ",".join(colnames) expand_columns = [] normal_columns = [] for colname in colnames: try: column = getattr(self, colname) except AttributeError: # The user specified a column that doesn't exist. Skip it. continue self.columns.append(column) column.active = True if column.expand: # This column is requesting all remaining space. Save it for # later so we can tell how much to give it. Each expanded # column will count as two normal columns when calculating # the normal sized columns. expand_columns.append(column) elif column.shrink: # Make this as small as possible. column.width = 0 else: # We'll divide the column widths equally after we've built # up the lists of expanded and normal sized columns. normal_columns.append(column) self.columns[-1].last = True # Try to figure out the column widths for each column. # We'll start with the normal sized columns. total_pct = 100 # Each expanded column counts as two normal columns. normal_column_width = total_pct / (len(self.columns) + len(expand_columns)) for column in normal_columns: column.width = normal_column_width total_pct -= normal_column_width if len(expand_columns) > 0: expanded_column_width = total_pct / len(expand_columns) else: expanded_column_width = 0 for column in expand_columns: column.width = expanded_column_width # Now get the sorting order for the columns. sort_str = self.request.GET.get('sort', profile_sort_list) if sort_str: self.sort_list = sort_str.split(',') else: self.sort_list = self.default_sort sort_str = ",".join(self.sort_list) # A subclass might have some work to do for loading and saving # as well. if self.load_extra_state(profile): profile_dirty = True # Now that we have all that, figure out if we need to save new # settings back to the profile. if profile: if self.profile_columns_field and \ colnames_str != profile_columns_list: setattr(profile, self.profile_columns_field, colnames_str) profile_dirty = True if self.profile_sort_field and sort_str != profile_sort_list: setattr(profile, self.profile_sort_field, sort_str) profile_dirty = True if profile_dirty: profile.save() self.state_loaded = True # Fetch the list of objects and have it ready. self.precompute_objects() def load_extra_state(self, profile): """ Loads any extra state needed for this grid. This is used by subclasses that may have additional data to load and save. This should return True if any profile-stored state has changed, or False otherwise. """ return False def precompute_objects(self): """ Builds the queryset and stores the list of objects for use in rendering the datagrid. """ query = self.queryset use_select_related = False # Generate the actual list of fields we'll be sorting by sort_list = [] for sort_item in self.sort_list: if sort_item[0] == "-": base_sort_item = sort_item[1:] prefix = "-" else: base_sort_item = sort_item prefix = "" if sort_item and base_sort_item in self.db_field_map: db_field = self.db_field_map[base_sort_item] sort_list.append(prefix + db_field) # Lookups spanning tables require that we query from those # tables. In order to keep things simple, we'll just use # select_related so that we don't have to figure out the # table relationships. We only do this if we have a lookup # spanning tables. if '.' in db_field: use_select_related = True if sort_list: query = query.order_by(*sort_list) self.paginator = QuerySetPaginator(query.distinct(), self.paginate_by, self.paginate_orphans) page_num = self.request.GET.get('page', 1) # Accept either "last" or a valid page number. if page_num == "last": page_num = self.paginator.num_pages try: self.page = self.paginator.page(page_num) except InvalidPage: raise Http404 self.id_list = [] if self.optimize_sorts and len(sort_list) > 0: # This can be slow when sorting by multiple columns. If we # have multiple items in the sort list, we'll request just the # IDs and then fetch the actual details from that. self.id_list = list( self.page.object_list.values_list('pk', flat=True)) # Make sure to unset the order. We can't meaningfully order these # results in the query, as what we really want is to keep it in # the order specified in id_list, and we certainly don't want # the database to do any special ordering (possibly slowing things # down). We'll set the order properly in a minute. self.page.object_list = self.post_process_queryset( self.queryset.model.objects.filter( pk__in=self.id_list).order_by()) if use_select_related: self.page.object_list = \ self.page.object_list.select_related(depth=1) if self.id_list: # The database will give us the items in a more or less random # order, since it doesn't know to keep it in the order provided by # the ID list. This will place the results back in the order we # expect. index = dict([(id, pos) for (pos, id) in enumerate(self.id_list)]) object_list = [None] * len(self.id_list) for obj in list(self.page.object_list): object_list[index[obj.pk]] = obj else: # Grab the whole list at once. We know it won't be too large, # and it will prevent one query per row. object_list = list(self.page.object_list) for column in self.columns: column.collect_objects(object_list) self.rows = [{ 'object': obj, 'cells': [column.render_cell(obj) for column in self.columns] } for obj in object_list if obj is not None] def post_process_queryset(self, queryset): """ Processes a QuerySet after the initial query has been built and pagination applied. This is only used when optimizing a sort. By default, this just returns the existing queryset. Custom datagrid subclasses can override this to add additional queries (such as subqueries in an extra() call) for use in the cell renderers. When optimize_sorts is True, subqueries (using extra()) on the initial QuerySet passed to the datagrid will be stripped from the final result. This function can be used to re-add those subqueries. """ for column in self.columns: queryset = column.augment_queryset(queryset) return queryset def render_listview(self): """ Renders the standard list view of the grid. This can be called from templates. """ self.load_state() context = { 'datagrid': self, 'is_paginated': self.page.has_other_pages(), 'results_per_page': self.paginate_by, 'has_next': self.page.has_next(), 'has_previous': self.page.has_previous(), 'page': self.page.number, 'next': self.page.next_page_number(), 'previous': self.page.previous_page_number(), 'last_on_page': self.page.end_index(), 'first_on_page': self.page.start_index(), 'pages': self.paginator.num_pages, 'hits': self.paginator.count, 'page_range': self.paginator.page_range, } context.update(self.extra_context) return mark_safe( render_to_string(self.listview_template, RequestContext(self.request, context))) def render_listview_to_response(self, request=None): """ Renders the listview to a response, preventing caching in the process. """ response = HttpResponse(unicode(self.render_listview())) patch_cache_control(response, no_cache=True, no_store=True, max_age=0, must_revalidate=True) return response def render_to_response(self, template_name, extra_context={}): """ Renders a template containing this datagrid as a context variable. """ self.load_state() # If the caller is requesting just this particular grid, return it. if self.request.GET.get('gridonly', False) and \ self.request.GET.get('datagrid-id', None) == self.id: return self.render_listview_to_response() context = {'datagrid': self} context.update(extra_context) context.update(self.extra_context) return render_to_response(template_name, RequestContext(self.request, context)) @staticmethod def link_to_object(obj, value): return obj.get_absolute_url() @staticmethod def link_to_value(obj, value): return value.get_absolute_url()
class RelatedDataGrid(DataGrid): """Version of djblets DataGrid modified for BPG needs This class is almost identical to it's superclass, DataGrid. The only differences are that the precomputer_objects and render_listview methods have been overridden. However, the code in both methods is very similar to the DataGrid superclass code for these methods. Small, customized 'tweaks' have been made to the original and are documented per method. """ def precompute_objects(self): """ Builds the queryset and stores the list of objects for use in rendering the datagrid. Just like the method in the superclass, except we use self.depth as the depth for select_related """ query = self.queryset use_select_related = False # Generate the actual list of fields we'll be sorting by sort_list = [] for sort_item in self.sort_list: if sort_item[0] == "-": base_sort_item = sort_item[1:] prefix = "-" else: base_sort_item = sort_item prefix = "" if sort_item and base_sort_item in self.db_field_map: db_field = self.db_field_map[base_sort_item] sort_list.append(prefix + db_field) # Lookups spanning tables require that we query from those # tables. In order to keep things simple, we'll just use # select_related so that we don't have to figure out the # table relationships. We only do this if we have a lookup # spanning tables. if '.' in db_field: use_select_related = True if sort_list: query = query.order_by(*sort_list) if use_select_related: query = query.select_related(depth=self.depth) self.paginator = QuerySetPaginator(query, self.paginate_by, self.paginate_orphans) page_num = self.request.GET.get('page', 1) # Accept either "last" or a valid page number. if page_num == "last": page_num = self.paginator.num_pages try: self.page = self.paginator.page(page_num) except InvalidPage: raise Http404 self.rows = [] for obj in self.page.object_list: self.rows.append({ 'object': obj, 'cells': [column.render_cell(obj) for column in self.columns] }) def render_listview(self): """ Renders the standard list view of the grid. This can be called from templates. Just like the method in the superclass, except it updates the context with self.extra_context first. """ self.load_state() context = RequestContext( self.request, { 'datagrid': self, 'is_paginated': self.page.has_other_pages(), 'results_per_page': self.paginate_by, 'has_next': self.page.has_next(), 'has_previous': self.page.has_previous(), 'page': self.page.number, 'next': self.page.next_page_number(), 'previous': self.page.previous_page_number(), 'last_on_page': self.page.end_index(), 'first_on_page': self.page.start_index(), 'pages': self.paginator.num_pages, 'hits': self.paginator.count, 'page_range': self.paginator.page_range, }) context.update(self.extra_context) return mark_safe(render_to_string(self.listview_template, context))
def object_list( request, queryset, paginate_by=None, page=None, allow_empty=True, template_name=None, template_loader=loader, extra_context=None, context_processors=None, template_object_name="object", mimetype=None, ): """ Generic list of objects. Templates: ``<app_label>/<model_name>_list.html`` Context: object_list list of objects is_paginated are the results paginated? results_per_page number of objects per page (if paginated) has_next is there a next page? has_previous is there a prev page? page the current page next the next page previous the previous page pages number of pages, total hits number of objects, total last_on_page the result number of the last of object in the object_list (1-indexed) first_on_page the result number of the first object in the object_list (1-indexed) page_range: A list of the page numbers (1-indexed). """ if extra_context is None: extra_context = {} queryset = queryset._clone() if paginate_by: paginator = QuerySetPaginator(queryset, paginate_by, allow_empty_first_page=allow_empty) if not page: page = request.GET.get("page", 1) try: page_number = int(page) except ValueError: if page == "last": page_number = paginator.num_pages else: # Page is not 'last', nor can it be converted to an int. raise Http404 try: page_obj = paginator.page(page_number) except InvalidPage: raise Http404 c = RequestContext( request, { "%s_list" % template_object_name: page_obj.object_list, "paginator": paginator, "page_obj": page_obj, # Legacy template context stuff. New templates should use page_obj # to access this instead. "is_paginated": page_obj.has_other_pages(), "results_per_page": paginator.per_page, "has_next": page_obj.has_next(), "has_previous": page_obj.has_previous(), "page": page_obj.number, "next": page_obj.next_page_number(), "previous": page_obj.previous_page_number(), "first_on_page": page_obj.start_index(), "last_on_page": page_obj.end_index(), "pages": paginator.num_pages, "hits": paginator.count, "page_range": paginator.page_range, }, context_processors, ) else: c = RequestContext( request, {"%s_list" % template_object_name: queryset, "paginator": None, "page_obj": None, "is_paginated": False}, context_processors, ) if not allow_empty and len(queryset) == 0: raise Http404 for key, value in extra_context.items(): if callable(value): c[key] = value() else: c[key] = value if not template_name: model = queryset.model template_name = "%s/%s_list.html" % (model._meta.app_label, model._meta.object_name.lower()) t = template_loader.get_template(template_name) return HttpResponse(t.render(c), mimetype=mimetype)
def object_list(request, queryset, paginate_by=None, page=None, allow_empty=True, template_name=None, template_loader=loader, extra_context=None, context_processors=None, template_object_name='object', mimetype=None): """ Generic list of objects. Templates: ``<app_label>/<model_name>_list.html`` Context: object_list list of objects is_paginated are the results paginated? results_per_page number of objects per page (if paginated) has_next is there a next page? has_previous is there a prev page? page the current page next the next page previous the previous page pages number of pages, total hits number of objects, total last_on_page the result number of the last of object in the object_list (1-indexed) first_on_page the result number of the first object in the object_list (1-indexed) page_range: A list of the page numbers (1-indexed). """ if extra_context is None: extra_context = {} queryset = queryset._clone() if paginate_by: paginator = QuerySetPaginator(queryset, paginate_by, allow_empty_first_page=allow_empty) if not page: page = request.GET.get('page', 1) try: page_number = int(page) except ValueError: if page == 'last': page_number = paginator.num_pages else: # Page is not 'last', nor can it be converted to an int. raise Http404 try: page_obj = paginator.page(page_number) except InvalidPage: raise Http404 c = RequestContext( request, { '%s_list' % template_object_name: page_obj.object_list, 'paginator': paginator, 'page_obj': page_obj, # Legacy template context stuff. New templates should use page_obj # to access this instead. 'is_paginated': page_obj.has_other_pages(), 'results_per_page': paginator.per_page, 'has_next': page_obj.has_next(), 'has_previous': page_obj.has_previous(), 'page': page_obj.number, 'next': page_obj.next_page_number(), 'previous': page_obj.previous_page_number(), 'first_on_page': page_obj.start_index(), 'last_on_page': page_obj.end_index(), 'pages': paginator.num_pages, 'hits': paginator.count, 'page_range': paginator.page_range, }, context_processors) else: c = RequestContext( request, { '%s_list' % template_object_name: queryset, 'paginator': None, 'page_obj': None, 'is_paginated': False, }, context_processors) if not allow_empty and len(queryset) == 0: raise Http404 for key, value in extra_context.items(): if callable(value): c[key] = value() else: c[key] = value if not template_name: model = queryset.model template_name = "%s/%s_list.html" % (model._meta.app_label, model._meta.object_name.lower()) t = template_loader.get_template(template_name) return HttpResponse(t.render(c), mimetype=mimetype)
class DataGrid(object): """ A representation of a list of objects, sorted and organized by columns. The sort order and column lists can be customized. allowing users to view this data however they prefer. This is meant to be subclassed for specific uses. The subclasses are responsible for defining one or more column types. It can also set one or more of the following optional variables: * 'title': The title of the grid. * 'profile_sort_field': The variable name in the user profile where the sort order can be loaded and saved. * 'profile_columns_field": The variable name in the user profile where the columns list can be loaded and saved. * 'paginate_by': The number of items to show on each page of the grid. The default is 50. * 'paginate_orphans': If this number of objects or fewer are on the last page, it will be rolled into the previous page. The default is 3. * 'page': The page to display. If this is not specified, the 'page' variable passed in the URL will be used, or 1 if that is not specified. * 'listview_template': The template used to render the list view. The default is 'datagrid/listview.html' * 'column_header_template': The template used to render each column header. The default is 'datagrid/column_header.html' * 'cell_template': The template used to render a cell of data. The default is 'datagrid/cell.html' * 'optimize_sorts': Whether or not to optimize queries when using multiple sorts. This can offer a speed improvement, but may need to be turned off for more advanced querysets (such as when using extra()). The default is True. """ _columns = None @classmethod def add_column(cls, column): """Adds a new column for this datagrid. This can be used to add columns to a DataGrid subclass after the subclass has already been defined. The column added must have a unique ID already set. """ cls._populate_columns() if not column.id: raise KeyError( 'Custom datagrid columns must have a unique id attribute.') if column.id in _column_registry[cls]: raise KeyError('"%s" is already a registered column for %s' % (column.id, cls.__name__)) _column_registry[cls][column.id] = column @classmethod def remove_column(cls, column): """Removes a column from this datagrid. This can be used to remove columns previously added through add_column(). """ cls._populate_columns() try: del _column_registry[cls][column.id] except KeyError: raise KeyError('"%s" is not a registered column for %s' % (column.id, cls.__name__)) @classmethod def get_column(cls, column_id): """Returns the column with the given ID. If not found, this will return None. """ cls._populate_columns() return _column_registry[cls].get(column_id) @classmethod def get_columns(cls): """Returns the list of registered columns for this datagrid.""" cls._populate_columns() return six.itervalues(_column_registry[cls]) @classmethod def _populate_columns(cls): """Populates the default list of columns for the datagrid. The default list contains all columns added in the class definition. """ if cls not in _column_registry: _column_registry[cls] = {} for key in dir(cls): column = getattr(cls, key) if isinstance(column, Column): column.id = key if not column.field_name: column.field_name = column.id if not column.db_field: column.db_field = column.field_name cls.add_column(column) def __init__(self, request, queryset=None, title="", extra_context={}, optimize_sorts=True): self.request = request self.queryset = queryset self.rows = [] self.columns = [] self.column_map = {} self.id_list = [] self.paginator = None self.page = None self.sort_list = None self.state_loaded = False self.page_num = 0 self.id = None self.extra_context = dict(extra_context) self.optimize_sorts = optimize_sorts self.special_query_args = [] if not hasattr(request, "datagrid_count"): request.datagrid_count = 0 self.id = "datagrid-%s" % request.datagrid_count request.datagrid_count += 1 # Customizable variables self.title = title self.profile_sort_field = None self.profile_columns_field = None self.paginate_by = 50 self.paginate_orphans = 3 self.listview_template = 'datagrid/listview.html' self.column_header_template = 'datagrid/column_header.html' self.cell_template = 'datagrid/cell.html' self.paginator_template = 'datagrid/paginator.html' @cached_property def cell_template_obj(self): obj = get_template(self.cell_template) if not obj: logging.error("Unable to load template '%s' for datagrid " "cell. This may be an installation issue.", self.cell_template, extra={ 'request': self.request, }) return obj @cached_property def column_header_template_obj(self): obj = get_template(self.column_header_template) if not obj: logging.error("Unable to load template '%s' for datagrid " "column headers. This may be an installation " "issue.", self.column_header_template, extra={ 'request': self.request, }) return obj @property def all_columns(self): """Returns all columns in the datagrid, sorted by label.""" return [ self.get_stateful_column(column) for column in sorted(self.get_columns(), key=lambda x: x.detailed_label) ] def get_stateful_column(self, column): """Returns a StatefulColumn for the given Column instance. If one has already been created, it will be returned. """ if column not in self.column_map: self.column_map[column] = StatefulColumn(self, column) return self.column_map[column] def load_state(self, render_context=None): """ Loads the state of the datagrid. This will retrieve the user-specified or previously stored sorting order and columns list, as well as any state a subclass may need. """ if self.state_loaded: return profile_sort_list = None profile_columns_list = None profile = None profile_dirty = False # Get the saved settings for this grid in the profile. These will # work as defaults and allow us to determine if we need to save # the profile. if self.request.user.is_authenticated(): try: profile = self.request.user.get_profile() if self.profile_sort_field: profile_sort_list = \ getattr(profile, self.profile_sort_field, None) if self.profile_columns_field: profile_columns_list = \ getattr(profile, self.profile_columns_field, None) except SiteProfileNotAvailable: pass except ObjectDoesNotExist: pass # Figure out the columns we're going to display # We're also going to calculate the column widths based on the # shrink and expand values. colnames_str = self.request.GET.get('columns', profile_columns_list) if colnames_str: colnames = colnames_str.split(',') else: colnames = self.default_columns colnames_str = ",".join(colnames) expand_columns = [] normal_columns = [] for colname in colnames: column_def = self.get_column(colname) if not column_def: # The user specified a column that doesn't exist. Skip it. continue column = self.get_stateful_column(column_def) self.columns.append(column) column.active = True if column.expand: # This column is requesting all remaining space. Save it for # later so we can tell how much to give it. Each expanded # column will count as two normal columns when calculating # the normal sized columns. expand_columns.append(column) elif column.shrink: # Make this as small as possible. column.width = 0 else: # We'll divide the column widths equally after we've built # up the lists of expanded and normal sized columns. normal_columns.append(column) self.columns[-1].last = True # Try to figure out the column widths for each column. # We'll start with the normal sized columns. total_pct = 100 # Each expanded column counts as two normal columns. normal_column_width = total_pct / (len(self.columns) + len(expand_columns)) for column in normal_columns: column.width = normal_column_width total_pct -= normal_column_width if len(expand_columns) > 0: expanded_column_width = total_pct / len(expand_columns) else: expanded_column_width = 0 for column in expand_columns: column.width = expanded_column_width # Now get the sorting order for the columns. sort_str = self.request.GET.get('sort', profile_sort_list) if sort_str: self.sort_list = sort_str.split(',') else: self.sort_list = self.default_sort sort_str = ",".join(self.sort_list) # A subclass might have some work to do for loading and saving # as well. if self.load_extra_state(profile): profile_dirty = True # Now that we have all that, figure out if we need to save new # settings back to the profile. if profile: if (self.profile_columns_field and colnames_str != profile_columns_list): setattr(profile, self.profile_columns_field, colnames_str) profile_dirty = True if self.profile_sort_field and sort_str != profile_sort_list: setattr(profile, self.profile_sort_field, sort_str) profile_dirty = True if profile_dirty: profile.save() self.state_loaded = True # Fetch the list of objects and have it ready. self.precompute_objects(render_context) def load_extra_state(self, profile): """ Loads any extra state needed for this grid. This is used by subclasses that may have additional data to load and save. This should return True if any profile-stored state has changed, or False otherwise. """ return False def precompute_objects(self, render_context=None): """ Builds the queryset and stores the list of objects for use in rendering the datagrid. """ query = self.queryset use_select_related = False # Generate the actual list of fields we'll be sorting by sort_list = [] for sort_item in self.sort_list: if sort_item[0] == "-": base_sort_item = sort_item[1:] prefix = "-" else: base_sort_item = sort_item prefix = "" if sort_item: column = self.get_column(base_sort_item) if not column: logging.warning('Skipping non-existing sort column "%s" ' 'for user "%s".', base_sort_item, self.request.user.username) continue stateful_column = self.get_stateful_column(column) if stateful_column: try: sort_field = stateful_column.get_sort_field() except Exception as e: logging.error('Error when calling get_sort_field for ' 'DataGrid Column %r: %s', column, e, exc_info=1) sort_field = '' sort_list.append(prefix + sort_field) # Lookups spanning tables require that we query from those # tables. In order to keep things simple, we'll just use # select_related so that we don't have to figure out the # table relationships. We only do this if we have a lookup # spanning tables. if '.' in sort_field: use_select_related = True if sort_list: query = query.order_by(*sort_list) query = self.post_process_queryset(query) self.paginator = QuerySetPaginator(query.distinct(), self.paginate_by, self.paginate_orphans) page_num = self.request.GET.get('page', 1) # Accept either "last" or a valid page number. if page_num == "last": page_num = self.paginator.num_pages try: self.page = self.paginator.page(page_num) except InvalidPage: raise Http404 self.id_list = [] if self.optimize_sorts and len(sort_list) > 0: # This can be slow when sorting by multiple columns. If we # have multiple items in the sort list, we'll request just the # IDs and then fetch the actual details from that. self.id_list = list(self.page.object_list.values_list( 'pk', flat=True)) # Make sure to unset the order. We can't meaningfully order these # results in the query, as what we really want is to keep it in # the order specified in id_list, and we certainly don't want # the database to do any special ordering (possibly slowing things # down). We'll set the order properly in a minute. self.page.object_list = self.post_process_queryset( self.queryset.model.objects.filter( pk__in=self.id_list).order_by()) if use_select_related: self.page.object_list = \ self.page.object_list.select_related(depth=1) if self.id_list: # The database will give us the items in a more or less random # order, since it doesn't know to keep it in the order provided by # the ID list. This will place the results back in the order we # expect. index = dict([(id, pos) for (pos, id) in enumerate(self.id_list)]) object_list = [None] * len(self.id_list) for obj in list(self.page.object_list): object_list[index[obj.pk]] = obj else: # Grab the whole list at once. We know it won't be too large, # and it will prevent one query per row. object_list = list(self.page.object_list) for column in self.columns: column.collect_objects(object_list) if render_context is None: render_context = self._build_render_context() try: self.rows = [] for obj in object_list: if obj is None: continue if hasattr(obj, 'get_absolute_url'): obj_url = obj.get_absolute_url() else: obj_url = None render_context['_datagrid_object_url'] = obj_url self.rows.append({ 'object': obj, 'cells': [column.render_cell(obj, render_context) for column in self.columns] }) except Exception as e: logging.error('Error when calling render_cell for DataGrid ' 'Column %r: %s', column, e, exc_info=1) def post_process_queryset(self, queryset): """Add column-specific data to the queryset. Individual columns can define additional joins and extra info to add on to the queryset. This handles adding all of those. """ for column in self.columns: try: queryset = column.augment_queryset(queryset) except Exception as e: logging.error('Error when calling augment_queryset for ' 'DataGrid Column %r: %s', column, e, exc_info=1) return queryset def render_listview(self, render_context=None): """ Renders the standard list view of the grid. This can be called from templates. """ try: if render_context is None: render_context = self._build_render_context() self.load_state(render_context) context = { 'datagrid': self, } context.update(self.extra_context) context.update(render_context) return mark_safe(render_to_string(self.listview_template, Context(context))) except Exception: trace = traceback.format_exc() logging.error('Failed to render datagrid:\n%s' % trace, extra={ 'request': self.request, }) return mark_safe('<pre>%s</pre>' % trace) def render_listview_to_response(self, request=None, render_context=None): """ Renders the listview to a response, preventing caching in the process. """ response = HttpResponse( six.text_type(self.render_listview(render_context))) patch_cache_control(response, no_cache=True, no_store=True, max_age=0, must_revalidate=True) return response def render_to_response(self, template_name, extra_context={}): """ Renders a template containing this datagrid as a context variable. """ render_context = self._build_render_context() self.load_state(render_context) # If the caller is requesting just this particular grid, return it. if self.request.GET.get('gridonly', False) and \ self.request.GET.get('datagrid-id', None) == self.id: return self.render_listview_to_response( render_context=render_context) context = { 'datagrid': self } context.update(extra_context) context.update(render_context) return render_to_response(template_name, Context(context)) def render_paginator(self, adjacent_pages=3): """Renders the paginator for the datagrid. This can be called from templates. """ extra_query = get_url_params_except(self.request.GET, 'page', 'gridonly', *self.special_query_args) page_nums = range(max(1, self.page.number - adjacent_pages), min(self.paginator.num_pages, self.page.number + adjacent_pages) + 1) if extra_query: extra_query += '&' context = { 'is_paginated': self.page.has_other_pages(), 'hits': self.paginator.count, 'results_per_page': self.paginate_by, 'page': self.page.number, 'pages': self.paginator.num_pages, 'page_numbers': page_nums, 'has_next': self.page.has_next(), 'has_previous': self.page.has_previous(), 'show_first': 1 not in page_nums, 'show_last': self.paginator.num_pages not in page_nums, 'extra_query': extra_query, } if self.page.has_next(): context['next'] = self.page.next_page_number() else: context['next'] = None if self.page.has_previous(): context['previous'] = self.page.previous_page_number() else: context['previous'] = None context.update(self.extra_context) return mark_safe(render_to_string(self.paginator_template, Context(context))) def _build_render_context(self): """Builds a dictionary containing RequestContext contents. A RequestContext can be expensive, so it's best to reuse the contents of one when possible. This is not easy with a standard RequestContext, but it's possible to build one and then pull out the contents into a dictionary. """ request_context = RequestContext(self.request) render_context = {} for d in request_context: render_context.update(d) return render_context @staticmethod def link_to_object(state, obj, value): return obj.get_absolute_url() @staticmethod def link_to_value(state, obj, value): return value.get_absolute_url()
def browse(request, order_id): order = get_object_or_404(Order, id=order_id) Product = order.get_product_model() try: paginate_by=int(fetch_http_param(request, 'paginate_by', 25)) except ValueError: raise Http404 try: page_number = int(fetch_http_param(request, 'page', 1)) except ValueError: raise Http404 search = fetch_http_param(request, 'search', None) just_basket = fetch_http_param(request, 'just_basket', None) just_splittables = fetch_http_param(request, 'just_splittables', None) brand_slug = fetch_http_param(request, 'brand', None) code = fetch_http_param(request, 'code', None) productCategory_slug = request.GET.get('productCategory', None) productCategory_slug = fetch_http_param(request, 'productCategory', None) try: productCategory = ProductCategory.objects.get(slug=productCategory_slug, catalogue=order.catalogue) except ProductCategory.DoesNotExist: productCategory = None kwargs={'catalogue':order.catalogue} if brand_slug: kwargs['brand_slug'] = brand_slug if productCategory: kwargs['category'] = productCategory if code: kwargs['supplier_product_code__istartswith']=code if search: searchBits=search.split() else: searchBits=[] if just_basket: # This is probably a bit crap: basket_item_ids = [i.product.id for i in request.user.get_profile().basket_set.get(order=order).basketitem_set.all()] if basket_item_ids: filteredProducts = Product.objects.filter(id__in=basket_item_ids) else: filteredProducts = Product.objects.exclude(id__gt=0) else: filteredProducts = Product.objects.filter(**kwargs) for s in searchBits: filteredProducts = filteredProducts.filter(Q(name__icontains=s) | Q(category__name__icontains=s) | Q(brand_name__icontains=s)) if just_splittables: filteredProducts = filteredProducts.filter(outgoingUnitsPerIncomingUnit__gte=2) paginator = QuerySetPaginator(filteredProducts, paginate_by) page = paginator.page(page_number) url_params = { 'brand': brand_slug, 'productCategory':productCategory_slug, 'code':code, 'search':search, 'paginate_by':str(paginate_by), 'just_basket':just_basket, 'page':str(page_number), } current_url = request.path+'?'+make_querystring(url_params) url_params['page'] = str(page_number-1) previous_url = request.path+'?'+make_querystring(url_params) url_params['page'] = str(page_number+1) next_url = request.path+'?'+make_querystring(url_params) brands = cache.get('catalogue_%d_brands' % order.catalogue.id) if not brands: # Refresh brands list... cursor = connection.cursor() cursor.execute("""select brand_slug, brand_name from catalogue_baseproduct where brand_slug != '' and catalogue_id=%s group by brand_slug, brand_name order by brand_name""", (order.catalogue.id,)) brands = cursor.fetchall() cache.set('catalogue_%d_brands' % order.catalogue.id, brands, 60*60*24*1) return render_to_response('catalogue/browse.html', { 'order' : order, 'productCategories' : ProductCategory.objects.filter(catalogue=order.catalogue), 'productCategory' : productCategory, 'brand' : brand_slug, 'brands' : brands, 'search' : search, 'paginator' : paginator, 'page': page, 'next_url':next_url, 'previous_url':previous_url, 'current_url':current_url, 'code' : code, 'just_basket' : just_basket, 'total_basket_price':total_basket_price(request.user.username, order.id), }, context_instance=RequestContext(request) )
class DataGrid(object): """ A representation of a list of objects, sorted and organized by columns. The sort order and column lists can be customized. allowing users to view this data however they prefer. This is meant to be subclassed for specific uses. The subclasses are responsible for defining one or more column types. It can also set one or more of the following optional variables: * 'title': The title of the grid. * 'profile_sort_field': The variable name in the user profile where the sort order can be loaded and saved. * 'profile_columns_field": The variable name in the user profile where the columns list can be loaded and saved. * 'paginate_by': The number of items to show on each page of the grid. The default is 50. * 'paginate_orphans': If this number of objects or fewer are on the last page, it will be rolled into the previous page. The default is 3. * 'page': The page to display. If this is not specified, the 'page' variable passed in the URL will be used, or 1 if that is not specified. * 'listview_template': The template used to render the list view. The default is 'datagrid/listview.html' * 'column_header_template': The template used to render each column header. The default is 'datagrid/column_header.html' * 'cell_template': The template used to render a cell of data. The default is 'datagrid/cell.html' * 'optimize_sorts': Whether or not to optimize queries when using multiple sorts. This can offer a speed improvement, but may need to be turned off for more advanced querysets (such as when using extra()). The default is True. """ _columns = None @classmethod def add_column(cls, column): """Adds a new column for this datagrid. This can be used to add columns to a DataGrid subclass after the subclass has already been defined. The column added must have a unique ID already set. """ cls._populate_columns() if not column.id: raise KeyError( 'Custom datagrid columns must have a unique id attribute.') if column.id in _column_registry[cls]: raise KeyError('"%s" is already a registered column for %s' % (column.id, cls.__name__)) _column_registry[cls][column.id] = column @classmethod def remove_column(cls, column): """Removes a column from this datagrid. This can be used to remove columns previously added through add_column(). """ cls._populate_columns() try: del _column_registry[cls][column.id] except KeyError: raise KeyError('"%s" is not a registered column for %s' % (column.id, cls.__name__)) @classmethod def get_column(cls, column_id): """Returns the column with the given ID. If not found, this will return None. """ cls._populate_columns() return _column_registry[cls].get(column_id) @classmethod def get_columns(cls): """Returns the list of registered columns for this datagrid.""" cls._populate_columns() return six.itervalues(_column_registry[cls]) @classmethod def _populate_columns(cls): """Populates the default list of columns for the datagrid. The default list contains all columns added in the class definition. """ if cls not in _column_registry: _column_registry[cls] = {} for key in dir(cls): column = getattr(cls, key) if isinstance(column, Column): column.id = key if not column.field_name: column.field_name = column.id if not column.db_field: column.db_field = column.field_name cls.add_column(column) def __init__(self, request, queryset=None, title="", extra_context={}, optimize_sorts=True): self.request = request self.queryset = queryset self.rows = [] self.columns = [] self.column_map = {} self.id_list = [] self.paginator = None self.page = None self.sort_list = None self.state_loaded = False self.page_num = 0 self.id = None self.extra_context = dict(extra_context) self.optimize_sorts = optimize_sorts self.special_query_args = [] if not hasattr(request, "datagrid_count"): request.datagrid_count = 0 self.id = "datagrid-%s" % request.datagrid_count request.datagrid_count += 1 # Customizable variables self.title = title self.profile_sort_field = None self.profile_columns_field = None self.paginate_by = 50 self.paginate_orphans = 3 self.listview_template = 'datagrid/listview.html' self.column_header_template = 'datagrid/column_header.html' self.cell_template = 'datagrid/cell.html' self.paginator_template = 'datagrid/paginator.html' @cached_property def cell_template_obj(self): obj = get_template(self.cell_template) if not obj: logging.error( "Unable to load template '%s' for datagrid " "cell. This may be an installation issue.", self.cell_template, extra={ 'request': self.request, }) return obj @cached_property def column_header_template_obj(self): obj = get_template(self.column_header_template) if not obj: logging.error( "Unable to load template '%s' for datagrid " "column headers. This may be an installation " "issue.", self.column_header_template, extra={ 'request': self.request, }) return obj @property def all_columns(self): """Returns all columns in the datagrid, sorted by label.""" return [ self.get_stateful_column(column) for column in sorted(self.get_columns(), key=lambda x: x.detailed_label) ] def get_stateful_column(self, column): """Returns a StatefulColumn for the given Column instance. If one has already been created, it will be returned. """ if column not in self.column_map: self.column_map[column] = StatefulColumn(self, column) return self.column_map[column] def load_state(self, render_context=None): """ Loads the state of the datagrid. This will retrieve the user-specified or previously stored sorting order and columns list, as well as any state a subclass may need. """ if self.state_loaded: return profile_sort_list = None profile_columns_list = None profile = None profile_dirty = False # Get the saved settings for this grid in the profile. These will # work as defaults and allow us to determine if we need to save # the profile. if self.request.user.is_authenticated(): try: profile = self.request.user.get_profile() if self.profile_sort_field: profile_sort_list = \ getattr(profile, self.profile_sort_field, None) if self.profile_columns_field: profile_columns_list = \ getattr(profile, self.profile_columns_field, None) except SiteProfileNotAvailable: pass except ObjectDoesNotExist: pass # Figure out the columns we're going to display # We're also going to calculate the column widths based on the # shrink and expand values. colnames_str = self.request.GET.get('columns', profile_columns_list) if colnames_str: colnames = colnames_str.split(',') else: colnames = self.default_columns colnames_str = ",".join(colnames) expand_columns = [] normal_columns = [] for colname in colnames: column_def = self.get_column(colname) if not column_def: # The user specified a column that doesn't exist. Skip it. continue column = self.get_stateful_column(column_def) self.columns.append(column) column.active = True if column.expand: # This column is requesting all remaining space. Save it for # later so we can tell how much to give it. Each expanded # column will count as two normal columns when calculating # the normal sized columns. expand_columns.append(column) elif column.shrink: # Make this as small as possible. column.width = 0 else: # We'll divide the column widths equally after we've built # up the lists of expanded and normal sized columns. normal_columns.append(column) self.columns[-1].last = True # Try to figure out the column widths for each column. # We'll start with the normal sized columns. total_pct = 100 # Each expanded column counts as two normal columns. normal_column_width = total_pct / (len(self.columns) + len(expand_columns)) for column in normal_columns: column.width = normal_column_width total_pct -= normal_column_width if len(expand_columns) > 0: expanded_column_width = total_pct / len(expand_columns) else: expanded_column_width = 0 for column in expand_columns: column.width = expanded_column_width # Now get the sorting order for the columns. sort_str = self.request.GET.get('sort', profile_sort_list) if sort_str: self.sort_list = sort_str.split(',') else: self.sort_list = self.default_sort sort_str = ",".join(self.sort_list) # A subclass might have some work to do for loading and saving # as well. if self.load_extra_state(profile): profile_dirty = True # Now that we have all that, figure out if we need to save new # settings back to the profile. if profile: if (self.profile_columns_field and colnames_str != profile_columns_list): setattr(profile, self.profile_columns_field, colnames_str) profile_dirty = True if self.profile_sort_field and sort_str != profile_sort_list: setattr(profile, self.profile_sort_field, sort_str) profile_dirty = True if profile_dirty: profile.save() self.state_loaded = True # Fetch the list of objects and have it ready. self.precompute_objects(render_context) def load_extra_state(self, profile): """ Loads any extra state needed for this grid. This is used by subclasses that may have additional data to load and save. This should return True if any profile-stored state has changed, or False otherwise. """ return False def precompute_objects(self, render_context=None): """ Builds the queryset and stores the list of objects for use in rendering the datagrid. """ query = self.queryset use_select_related = False # Generate the actual list of fields we'll be sorting by sort_list = [] for sort_item in self.sort_list: if sort_item[0] == "-": base_sort_item = sort_item[1:] prefix = "-" else: base_sort_item = sort_item prefix = "" if sort_item: column = self.get_column(base_sort_item) if not column: logging.warning( 'Skipping non-existing sort column "%s" ' 'for user "%s".', base_sort_item, self.request.user.username) continue stateful_column = self.get_stateful_column(column) if stateful_column: try: sort_field = stateful_column.get_sort_field() except Exception as e: logging.error( 'Error when calling get_sort_field for ' 'DataGrid Column %r: %s', column, e, exc_info=1) sort_field = '' sort_list.append(prefix + sort_field) # Lookups spanning tables require that we query from those # tables. In order to keep things simple, we'll just use # select_related so that we don't have to figure out the # table relationships. We only do this if we have a lookup # spanning tables. if '.' in sort_field: use_select_related = True if sort_list: query = query.order_by(*sort_list) query = self.post_process_queryset(query) self.paginator = QuerySetPaginator(query.distinct(), self.paginate_by, self.paginate_orphans) page_num = self.request.GET.get('page', 1) # Accept either "last" or a valid page number. if page_num == "last": page_num = self.paginator.num_pages try: self.page = self.paginator.page(page_num) except InvalidPage: raise Http404 self.id_list = [] if self.optimize_sorts and len(sort_list) > 0: # This can be slow when sorting by multiple columns. If we # have multiple items in the sort list, we'll request just the # IDs and then fetch the actual details from that. self.id_list = list( self.page.object_list.values_list('pk', flat=True)) # Make sure to unset the order. We can't meaningfully order these # results in the query, as what we really want is to keep it in # the order specified in id_list, and we certainly don't want # the database to do any special ordering (possibly slowing things # down). We'll set the order properly in a minute. self.page.object_list = self.post_process_queryset( self.queryset.model.objects.filter( pk__in=self.id_list).order_by()) if use_select_related: self.page.object_list = \ self.page.object_list.select_related(depth=1) if self.id_list: # The database will give us the items in a more or less random # order, since it doesn't know to keep it in the order provided by # the ID list. This will place the results back in the order we # expect. index = dict([(id, pos) for (pos, id) in enumerate(self.id_list)]) object_list = [None] * len(self.id_list) for obj in list(self.page.object_list): object_list[index[obj.pk]] = obj else: # Grab the whole list at once. We know it won't be too large, # and it will prevent one query per row. object_list = list(self.page.object_list) for column in self.columns: column.collect_objects(object_list) if render_context is None: render_context = self._build_render_context() try: self.rows = [{ 'object': obj, 'cells': [ column.render_cell(obj, render_context) for column in self.columns ] } for obj in object_list if obj is not None] except Exception as e: logging.error( 'Error when calling render_cell for DataGrid ' 'Column %r: %s', column, e, exc_info=1) def post_process_queryset(self, queryset): """Add column-specific data to the queryset. Individual columns can define additional joins and extra info to add on to the queryset. This handles adding all of those. """ for column in self.columns: try: queryset = column.augment_queryset(queryset) except Exception as e: logging.error( 'Error when calling augment_queryset for ' 'DataGrid Column %r: %s', column, e, exc_info=1) return queryset def render_listview(self, render_context=None): """ Renders the standard list view of the grid. This can be called from templates. """ try: if render_context is None: render_context = self._build_render_context() self.load_state(render_context) context = { 'datagrid': self, } context.update(self.extra_context) context.update(render_context) return mark_safe( render_to_string(self.listview_template, Context(context))) except Exception: trace = traceback.format_exc() logging.error('Failed to render datagrid:\n%s' % trace, extra={ 'request': self.request, }) return mark_safe('<pre>%s</pre>' % trace) def render_listview_to_response(self, request=None, render_context=None): """ Renders the listview to a response, preventing caching in the process. """ response = HttpResponse( six.text_type(self.render_listview(render_context))) patch_cache_control(response, no_cache=True, no_store=True, max_age=0, must_revalidate=True) return response def render_to_response(self, template_name, extra_context={}): """ Renders a template containing this datagrid as a context variable. """ render_context = self._build_render_context() self.load_state(render_context) # If the caller is requesting just this particular grid, return it. if self.request.GET.get('gridonly', False) and \ self.request.GET.get('datagrid-id', None) == self.id: return self.render_listview_to_response( render_context=render_context) context = {'datagrid': self} context.update(extra_context) context.update(render_context) return render_to_response(template_name, Context(context)) def render_paginator(self, adjacent_pages=3): """Renders the paginator for the datagrid. This can be called from templates. """ extra_query = get_url_params_except(self.request.GET, 'page', *self.special_query_args) page_nums = range( max(1, self.page.number - adjacent_pages), min(self.paginator.num_pages, self.page.number + adjacent_pages) + 1) if extra_query: extra_query += '&' context = { 'is_paginated': self.page.has_other_pages(), 'hits': self.paginator.count, 'results_per_page': self.paginate_by, 'page': self.page.number, 'pages': self.paginator.num_pages, 'page_numbers': page_nums, 'has_next': self.page.has_next(), 'has_previous': self.page.has_previous(), 'show_first': 1 not in page_nums, 'show_last': self.paginator.num_pages not in page_nums, 'extra_query': extra_query, } if self.page.has_next(): context['next'] = self.page.next_page_number() else: context['next'] = None if self.page.has_previous(): context['previous'] = self.page.previous_page_number() else: context['previous'] = None context.update(self.extra_context) return mark_safe( render_to_string(self.paginator_template, Context(context))) def _build_render_context(self): """Builds a dictionary containing RequestContext contents. A RequestContext can be expensive, so it's best to reuse the contents of one when possible. This is not easy with a standard RequestContext, but it's possible to build one and then pull out the contents into a dictionary. """ request_context = RequestContext(self.request) render_context = {} for d in request_context: render_context.update(d) return render_context @staticmethod def link_to_object(state, obj, value): return obj.get_absolute_url() @staticmethod def link_to_value(state, obj, value): return value.get_absolute_url()
def archive_month(request, year, month, queryset, date_field, paginate_by=None, page=None, month_format='%b', template_name=None, template_loader=loader, extra_context=None, allow_empty=False, context_processors=None, template_object_name='object', mimetype=None, allow_future=False): """ Generic monthly archive view. A replacement for ``django.views.generic.date_based.archive_month`` which offers result-set pagination. Templates: ``<app_label>/<model_name>_archive_month.html`` Context: month: (date) this month next_month: (date) the first day of the next month, or None if the next month is in the future previous_month: (date) the first day of the previous month object_list: list of objects published in the given month paginator: ``django.core.paginator.Paginator`` object (Available only if paginate_by and page provided) page_obj: ``django.core.paginator.Page`` object (Available only if paginate_by and page provided) """ if extra_context is None: extra_context = {} try: date = datetime.date(*time.strptime(year+month, '%Y'+month_format)[:3]) except ValueError: raise Http404 model = queryset.model now = datetime.datetime.now() # Calculate first and last day of month, for use in a date-range lookup. first_day = date.replace(day=1) if first_day.month == 12: last_day = first_day.replace(year=first_day.year + 1, month=1) else: last_day = first_day.replace(month=first_day.month + 1) lookup_kwargs = {'%s__range' % date_field: (first_day, last_day)} # Only bother to check current date if the month isn't in the past and future objects are requested. if last_day >= now.date() and not allow_future: lookup_kwargs['%s__lte' % date_field] = now object_list = queryset.filter(**lookup_kwargs) if not object_list and not allow_empty: raise Http404 # Calculate the next month, if applicable. if allow_future: next_month = last_day + datetime.timedelta(days=1) elif last_day < datetime.date.today(): next_month = last_day + datetime.timedelta(days=1) else: next_month = None if paginate_by: paginator = QuerySetPaginator(object_list, per_page=paginate_by, allow_empty_first_page=allow_empty) if not page: page = request.GET.get('page', 1) try: page_number = int(page) except ValueError: if page == 'last': page_number = paginator.num_pages else: raise Http404 try: page_obj = paginator.page(page_number) except InvalidPage: raise Http404 c = RequestContext(request, { 'month': date, 'next_month': next_month, 'previous_month': first_day - datetime.timedelta(days=1), '%s_list' % template_object_name: page_obj.object_list, 'paginator': paginator, 'page_obj': page_obj, }, context_processors) else: c = RequestContext(request, { '%s_list' % template_object_name: object_list, 'month': date, 'next_month': next_month, 'previous_month': first_day - datetime.timedelta(days=1), 'paginator': None, 'page_obj': None, }, context_processors) if not template_name: template_name = "%s/%s_archive_month.html" % (model._meta.app_label, model._meta.object_name.lower()) t = template_loader.get_template(template_name) for key, value in extra_context.items(): if callable(value): c[key] = value() else: c[key] = value return HttpResponse(t.render(c), mimetype=mimetype)
def json_browse(request, page_number=1): paginator = QuerySetPaginator(DownloadLink.objects.filter(hidden=False), 4) page = paginator.page(page_number) from time import sleep return json_paginator_response(page)
def archive_week(request, year, week, queryset, date_field, paginate_by=None, page=None, template_name=None, template_loader=loader, extra_context=None, allow_empty=True, context_processors=None, template_object_name='object', mimetype=None, allow_future=False): """ Generic weekly archive view. A replacement for ``django.views.generic.date_based.archive_week`` which offers result-set pagination. Templates: ``<app_label>/<model_name>_archive_week.html`` Context: week: (date) this week object_list: list of objects published in the given week paginator: ``django.core.paginator.Paginator`` object (Available only if paginate_by and page are provided) page_obj: ``django.core.paginator.Page`` object (Available only if paginate_by and page are provided) """ if extra_context is None: extra_context = {} try: date = datetime.date(*time.strptime(year+'-0-'+week, '%Y-%w-%U')[:3]) except ValueError: raise Http404 model = queryset.model now = datetime.datetime.now() # Calculate first and last day of week, for use in a date-range lookup. first_day = date last_day = date + datetime.timedelta(days=7) lookup_kwargs = {'%s__range' % date_field: (first_day, last_day)} # Only bother to check current date if the week isn't in the past and future objects aren't requested. if last_day >= now.date() and not allow_future: lookup_kwargs['%s__lte' % date_field] = now object_list = queryset.filter(**lookup_kwargs) if not object_list and not allow_empty: raise Http404 if paginate_by: paginator = QuerySetPaginator(object_list, per_page=paginate_by, allow_empty_first_page=allow_empty) if not page: page = request.GET.get('page', 1) try: page_number = int(page) except ValueError: if page == 'last': page_number = paginator.num_pages else: raise Http404 try: page_obj = paginator.page(page_number) except InvalidPage: raise Http404 c = RequestContext(request, { 'week': date, '%s_list' % template_object_name: page_obj.object_list, 'paginator': paginator, 'page_obj': page_obj, }, context_processors) else: c = RequestContext(request, { '%s_list' % template_object_name: object_list, 'week': date, 'paginator': None, 'page_obj': None, }, context_processors) if not template_name: template_name = "%s/%s_archive_week.html" % (model._meta.app_label, model._meta.object_name.lower()) t = template_loader.get_template(template_name) for key, value in extra_context.items(): if callable(value): c[key] = value() else: c[key] = value return HttpResponse(t.render(c), mimetype=mimetype)
def archive_day(request, year, month, day, queryset, date_field, paginate_by=None, page=None, month_format='%b', day_format='%d', template_name=None, template_loader=loader, extra_context=None, allow_empty=False, context_processors=None, template_object_name='object', mimetype=None, allow_future=False): """ Generic daily archive view. A replacement for ``django.views.generic.date_based.archive_day`` which offers result-set pagination. Templates: ``<app_label>/<model_name>_archive_day.html`` Context: object_list: list of objects published that day day: (datetime) the day previous_day (datetime) the previous day next_day (datetime) the next day, or None if the current day is today paginator: ``django.core.paginator.Paginator`` object (Available only if paginate_by and page are provided) page_obj: ``django.core.paginator.Page`` object (Available only if paginate_by and page are provided) """ if extra_context is None: extra_context = {} try: date = datetime.date(*time.strptime(year+month+day, '%Y'+month_format+day_format)[:3]) except ValueError: raise Http404 model = queryset.model now = datetime.datetime.now() if isinstance(model._meta.get_field(date_field), DateTimeField): lookup_kwargs = {'%s__range' % date_field: (datetime.datetime.combine(date, datetime.time.min), datetime.datetime.combine(date, datetime.time.max))} else: lookup_kwargs = {date_field: date} # Only bother to check current date if the date isn't in the past and future objects aren't requested. if date >= now.date() and not allow_future: lookup_kwargs['%s__lte' % date_field] = now object_list = queryset.filter(**lookup_kwargs) if not allow_empty and not object_list: raise Http404 # Calculate the next day, if applicable. if allow_future: next_day = date + datetime.timedelta(days=1) elif date < datetime.date.today(): next_day = date + datetime.timedelta(days=1) else: next_day = None if paginate_by: paginator = QuerySetPaginator(object_list, per_page=paginate_by, allow_empty_first_page=allow_empty) if not page: page = request.GET.get('page', 1) try: page_number = int(page) except ValueError: if page == 'last': page_number = paginator.num_pages else: raise Http404 try: page_obj = paginator.page(page_number) except InvalidPage: raise Http404 c = RequestContext(request, { 'day': date, 'previous_day': date - datetime.timedelta(days=1), 'next_day': next_day, '%s_list' % template_object_name: page_obj.object_list, 'paginator': paginator, 'page_obj': page_obj, }, context_processors) else: c = RequestContext(request, { '%s_list' % template_object_name: object_list, 'day': date, 'previous_day': date - datetime.timedelta(days=1), 'next_day': next_day, 'paginator': None, 'page_obj': None, }, context_processors) if not template_name: template_name = "%s/%s_archive_day.html" % (model._meta.app_label, model._meta.object_name.lower()) t = template_loader.get_template(template_name) for key, value in extra_context.items(): if callable(value): c[key] = value() else: c[key] = value return HttpResponse(t.render(c), mimetype=mimetype)
def archive_year(request, year, queryset, date_field, template_name=None, template_loader=loader, extra_context=None, allow_empty=False, context_processors=None, template_object_name='object', mimetype=None, make_object_list=False, allow_future=False, paginate_by=None, page=None): """ Generic yearly archive view. A replacement for ``django.views.generic.date_based.archive_year`` which offers result-set pagination. Templates: ``<app_label>/<model_name>_archive_year.html`` Context: date_list List of months in this year with objects year This year object_list List of objects published in the given month (Only available if make_object_list argument is True) paginator ``django.core.paginator.Paginator`` object (Only available if make_object_list argument is True) page_obj ``django.core.paginator.Page`` object (Only available if make_object_list argument is True) """ if extra_context is None: extra_context = {} model = queryset.model now = datetime.datetime.now() lookup_kwargs = {'%s__year' % date_field: year} # Only bother to check current date if the year isn't in the past and future objects aren't requested. if int(year) >= now.year and not allow_future: lookup_kwargs['%s__lte' % date_field] = now date_list = queryset.filter(**lookup_kwargs).dates(date_field, 'month') if not date_list and not allow_empty: raise Http404 if make_object_list: object_list = queryset.filter(**lookup_kwargs) paginator = QuerySetPaginator(object_list, per_page=paginate_by, allow_empty_first_page=allow_empty) if not page: page = request.GET.get('page', 1) try: page_number = int(page) except ValueError: if page == 'last': page_number = paginator.num_pages else: raise Http404 try: page_obj = paginator.page(page_number) except InvalidPage: raise Http404 c = RequestContext(request, { 'date_list': date_list, 'year': year, '%s_list' % template_object_name: page_obj.object_list, 'paginator': paginator, 'page_obj': page_obj, }, context_processors) else: object_list = [] c = RequestContext(request, { 'date_list': date_list, 'year': year, '%s_list' % template_object_name: object_list, 'paginator': None, 'page_obj': None, }, context_processors) if not template_name: template_name = "%s/%s_archive_year.html" % (model._meta.app_label, model._meta.object_name.lower()) t = template_loader.get_template(template_name) for key, value in extra_context.items(): if callable(value): c[key] = value() else: c[key] = value return HttpResponse(t.render(c), mimetype=mimetype)