I have a paginate I am trying to get the index page from an object page (sort of pagination in reverse)
The get_paginated_posts returns a paginator for the model Post:
class PostManager(models.Manager):
def get_paginated_posts(self, request=None):
if request and request.user.has_perm('blog.change_post'):
posts = super(PostManager, self).filter(is_update=False)
else:
posts = super(PostManager, self).filter(publish=True, is_update=False)
return Paginator(posts, POSTS_PER_PAGE)
.
.
This is my model
class Post(models.Model):
.
.
.
def get_page(self, request=None):
paginator = Post.objects.get_paginated_posts(request)
for i in range(1, paginator.num_pages+1):
if self in paginator.page(i).object_list:
return i
pass
return False
My concern is the Post.objects.get_paginated_posts call in the get_page function.
Is it right to call Post class from an instance? Is there any other better way to do this possible?
Why cannot I call super(Post, self).objects.get_paginated_posts to do the same?
I understand that self.objects.get_paginated_posts wont work because of absent access for the object to its manager.
Solved
Final code as suggested by Tomasz Elendt:
class PostManager(models.Manager):
def get_paginated_posts(self, user=None):
if user and user.has_perm('blog.change_post'):
posts = super(PostManager, self).filter(is_update=False)
else:
posts = super(PostManager, self).filter(publish=True, is_update=False)
return Paginator(posts, POSTS_PER_PAGE)
class Post(models.Model):
.
def get_page(self, request=None):
return self._default_manager.filter(is_update = False, time__gt=self.time).count()/POSTS_PER_PAGE +1
#Just a one line now :P
It's not the best idea what you're doing. Try to imagine how many queries it'll be translated to -- in the worst case you'd need to retrieve all user's posts from database!
I assume that you have some predefined ordering in your Post model (the one that Paginator uses). Use that to obtain the number of user's posts that precede that specific post record. If you divide that number by the POSTS_PER_PAGE value you'll get your page number.
IMHO using PostManager in Post methods is ok. What's not ok is that you're passing request object to it while I think you should use user_id for that (and permission checking should be really part of a view logic).
EDIT: example
from django.db import models
from django.contrib.auth.models import User
POSTS_PER_PAGE = 10
class Post(models.Model):
"""
>>> from datetime import datetime, timedelta
>>> from django.db import connection
>>> from django.conf import settings
>>>
>>> user = User.objects.create_user("test", "test#domain.com")
>>> for i in xrange(100):
... p = Post.objects.create(author=user,
... pub_date=datetime.now() - timedelta(hours=i))
>>> post = Post.objects.all()[68]
>>> settings.DEBUG = True # monkey-patching settings - ugly
>>> connection.queries = [] # cleaning previous queries
>>> post.get_page()
7
>>> len(connection.queries) # print number of queries of `get_page` call
1
"""
pub_date = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(User)
class Meta:
ordering = ["-pub_date"]
def get_page(self):
return self._default_manager.filter(author__id=self.author_id).filter(
pub_date__gt=self.pub_date).count() / POSTS_PER_PAGE + 1
Related
Django-cms-saq is tested for 2.4.x. I'm trying to update the program to work with 3.0.X.
So far, I've updated all the imports but am coming across an unusual error. When I add a question (a plugin) to a page and hit publish, it creates two copies of the question in the database (viewable through the admin site). Deleting either copy removes both from the published page but leaves the question in edit mode.
How would I go about trouble shooting this?
I will include some of the files here. Please let me know if you need any other files.
Note that I am trying to add a multiple-choice question.
From models.py:
from django.db import models
from django.db.models import Max, Sum
from cms.models import CMSPlugin, Page, Placeholder
from cms.models.fields import PageField
from taggit.managers import TaggableManager
from djangocms_text_ckeditor.models import AbstractText
...
class Question(CMSPlugin):
QUESTION_TYPES = [
('S', 'Single-choice question'),
('M', 'Multi-choice question'),
('F', 'Free-text question'),
]
slug = models.SlugField(
help_text="A slug for identifying answers to this specific question "
"(allows multiple only for multiple languages)")
tags = TaggableManager(blank=True)
label = models.CharField(max_length=512, blank=True)
help_text = models.CharField(max_length=512, blank=True)
question_type = models.CharField(max_length=1, choices=QUESTION_TYPES)
optional = models.BooleanField(
default=False,
help_text="Only applies to free text questions",
)
depends_on_answer = models.ForeignKey(
Answer, null=True, blank=True, related_name='trigger_questions')
def copy_relations(self, oldinstance):
for answer in oldinstance.answers.all():
answer.pk = None
answer.question = self
answer.save()
self.depends_on_answer = oldinstance.depends_on_answer
#staticmethod
def all_in_tree(page):
root = page.get_root()
# Remember that there might be questions on the root page as well!
tree = root.get_descendants() | Page.objects.filter(id=root.id)
placeholders = Placeholder.objects.filter(page__in=tree)
return Question.objects.filter(placeholder__in=placeholders)
#staticmethod
def all_in_page(page):
placeholders = Placeholder.objects.filter(page=page)
return Question.objects.filter(placeholder__in=placeholders)
def score(self, answers):
if self.question_type == 'F':
return 0
elif self.question_type == 'S':
return self.answers.get(slug=answers).score
elif self.question_type == 'M':
answers_list = answers.split(',')
return sum([self.answers.get(slug=a).score for a in answers_list])
#property
def max_score(self):
if not hasattr(self, '_max_score'):
if self.question_type == "S":
self._max_score = self.answers.aggregate(
Max('score'))['score__max']
elif self.question_type == "M":
self._max_score = self.answers.aggregate(
Sum('score'))['score__sum']
else:
self._max_score = None # don't score free-text answers
return self._max_score
def percent_score_for_user(self, user):
if self.max_score:
try:
score = Submission.objects.get(
question=self.slug,
user=user,
).score
except Submission.DoesNotExist:
return 0
return 100.0 * score / self.max_score
else:
return None
def __unicode__(self):
return self.slug
...
From cms_plugins.py
import itertools
import operator
from django.contrib import admin
from django.utils.translation import ugettext as _
from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool
from cms_saq.models import Question, Answer, GroupedAnswer, Submission, \
FormNav, ProgressBar, SectionedScoring, ScoreSection, BulkAnswer, \
QuestionnaireText, SubmissionSetReview
from djangocms_text_ckeditor.cms_plugins import TextPlugin
from djangocms_text_ckeditor.models import Text
from bs4 import BeautifulSoup
...
class QuestionPlugin(CMSPluginBase):
model = Question
module = "SAQ"
inlines = [AnswerAdmin]
exclude = ('question_type',)
def render(self, context, instance, placeholder):
user = context['request'].user
submission_set = None
triggered = True
depends_on = None
if instance.depends_on_answer:
depends_on = instance.depends_on_answer.pk
try:
Submission.objects.get(
user=user,
question=instance.depends_on_answer.question.slug,
answer=instance.depends_on_answer.slug,
submission_set=submission_set,
)
triggered = True
except:
triggered = False
extra = {
'question': instance,
'answers': instance.answers.all(),
'triggered': triggered,
'depends_on': depends_on,
}
if user.is_authenticated():
try:
extra['submission'] = Submission.objects.get(
user=user,
question=instance.slug,
submission_set=submission_set,
)
except Submission.DoesNotExist:
pass
context.update(extra)
return context
def save_model(self, request, obj, form, change):
obj.question_type = self.question_type
super(QuestionPlugin, self).save_model(request, obj, form, change)
...
class MultiChoiceQuestionPlugin(QuestionPlugin):
name = "Multi Choice Question"
render_template = "cms_saq/multi_choice_question.html"
question_type = "M"
exclude = ('question_type', 'help_text')
...
plugin_pool.register_plugin(SingleChoiceQuestionPlugin)
plugin_pool.register_plugin(MultiChoiceQuestionPlugin)
plugin_pool.register_plugin(DropDownQuestionPlugin)
plugin_pool.register_plugin(GroupedDropDownQuestionPlugin)
plugin_pool.register_plugin(FreeTextQuestionPlugin)
plugin_pool.register_plugin(FreeNumberQuestionPlugin)
plugin_pool.register_plugin(FormNavPlugin)
plugin_pool.register_plugin(SubmissionSetReviewPlugin)
plugin_pool.register_plugin(SectionedScoringPlugin)
plugin_pool.register_plugin(ProgressBarPlugin)
plugin_pool.register_plugin(BulkAnswerPlugin)
plugin_pool.register_plugin(SessionDefinition)
plugin_pool.register_plugin(QuestionnaireTextPlugin)
plugin_pool.register_plugin(TranslatedTextPlugin)
From cms_app.py:
from cms.app_base import CMSApp
from cms.apphook_pool import apphook_pool
from django.utils.translation import ugettext_lazy as _
class CMSSaq(CMSApp):
name = _("Self Assessment")
urls = ["cms_saq.urls"]
apphook_pool.register(CMSSaq)
Additional information:
This duplicative observation creates an issue when trying get the question object via its slug Question.objects.get(slug=question_slug). Such a query should only return one question. What we get here is two questions returned.
import re
from django.http import HttpResponse, HttpResponseBadRequest
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_POST, require_GET
from django.views.decorators.cache import never_cache
from django.utils import simplejson, datastructures
from django.conf import settings
from cms_saq.models import Question, Answer, Submission, SubmissionSet
ANSWER_RE = re.compile(r'^[\w-]+(,[\w-]+)*$')
#require_POST
def _submit(request):
post_data = datastructures.MultiValueDict(request.POST)
submission_set_tag = post_data.pop('submission_set_tag', '')
for question_slug, answers in post_data.iteritems():
# validate the question
try:
question = Question.objects.get(
slug=question_slug,
#placeholder__page__publisher_is_draft=False,
)
except Question.DoesNotExist:
return HttpResponseBadRequest(
"Invalid question '%s'" % question_slug,
)
# check answers is a list of slugs
if question.question_type != 'F' and not ANSWER_RE.match(answers):
return HttpResponseBadRequest("Invalid answers: %s" % answers)
# validate and score the answer
try:
score = question.score(answers)
except Answer.DoesNotExist:
return HttpResponseBadRequest(
"Invalid answer '%s:%s'" % (question_slug, answers)
)
# save, but don't update submissions belonging to an existing set
filter_attrs = {
'user': request.user.id,
'question': question_slug,
'submission_set': None,
}
attrs = {'answer': answers, 'score': score}
rows = Submission.objects.filter(**filter_attrs).update(**attrs)
if not rows:
attrs.update(filter_attrs)
Submission.objects.create(**attrs)
# Create submission set if requested
if submission_set_tag:
submission_set_tag = submission_set_tag[0]
if submission_set_tag:
_create_submission_set(
request, submission_set_tag
)
return HttpResponse("OK")
If you create a page in CMS, and add plugins to it, once you hit publish the CMS creates a copy of each of those plugins as they are at that point in time. This gives the page a live version, and enables you to then make changes to it which are held in draft mode, until you hit publish again.
This allows you to have a draft version of the site where edits/changes can be made and finalised before making them public.
Seeing two copies for each plugin therefore, isn't a problem.
As a side note, I'd strongly recommend you join the CMS user group on Google Plus if you're developing CMS sites; https://plus.google.com/communities/107689498573071376044
update
Ok, so a plugin in CMS is attached to a placeholder which is on a page, and it's the page that has two version of itself. Because each plugin attaches to a page, you can use that relationship to filter your plugins.
If you register your plugin with admin, or to object calls on it you're just looking at what is stored in your table, which as I say, includes the draft and live version of plugins.
So when you're querying for your plugin do this;
questions = Question.objects.filter(placeholder__page__publisher_is_draft=True)
This will get you all the questions which are attached to draft pages.
The only drawback to this is that plugins are guaranteed to be attached to a Page() object unless the plugin is set to page_only = True but I've only ever been interested in plugins attached to pages so it works for me. See the docs for more info on this.
Furthermore, if you're ever adding CMS page links to any of your plugins, please don't forget to add a similar argument to your model field to ensure that you limit the page objects to draft only;
page_link = models.ForeignKey(
Page,
limit_choices_to={'publisher_is_draft': True},
help_text=_("Link to another page on the site."),
on_delete=models.SET_NULL,
related_name='myapp_page_link'
)
I was trying to replicate a url regex equivalent to:
url(r'^members/(?P<username>\w+)/$', 'profiles.views.single'),
While I understand I am taking the users that exist within my database from:
from django.contrib.auth.models import User
I am wondering if I can change this to something a little more unique. I've been reading the documentation from: https://docs.djangoproject.com/en/1.8/intro/tutorial04/ but am still lost.
My question is regarding whether or not I can use data from a model as the named group in the regex expression.
Example:
from django.db import models
from django.contrib.auth.models import User
class Address(models.Model):
user = models.ForeignKey(User)
unique_code = models.IntegerField(max_length = 5)
active = models.BooleanField(default = True)
Is it somehow possible to use that 'unique_code' field as a possible url in my regex to map like:
url(r'^members/(?P<unique_code>\w+)/$', 'profiles.views.single'),
I was trying something like this, but it obviously didn't work:
def single(request, unique_id):
try:
uid = get_object_or_404(Address, unique_id=unique_id)
final = uid
except:
raise Http404
return render_to_response('single_user.html', locals(), context_instance=RequestContext(request))
Try to replace unique_id in your function with unique_code
def single(request, unique_code):
try:
uid = get_object_or_404(Address, unique_code=unique_code)
final = uid
except:
raise Http404
return render_to_response('single_user.html', locals(), context_instance=RequestContext(request))
So I have a models file:
import datetime
from django.db import models
class Organization(models.Model):
name = models.CharField(max_length=128, unique=True);
description = models.TextField(blank=True);
location = models.CharField(max_length=256, blank=True);
contact_email = models.EmailField(max_length=128, unique=True);
org_type = models.ForeignKey('OrganizationType');
created_at = models.DateTimeField(editable=False);
updated_at = models.DateTimeField();
def save(self, *args, **kwargs):
''' On save, update timestamps '''
datetime_now = datetime.datetime.now();
# If there's no ID, it's new
if not self.id:
self.created_at = datetime_now;
# Always update the modified at value
self.modified_at = datetime_now;
return super(User, self).save(*args, **kwargs);
class Meta:
app_label = 'bc';
And a view file Organization.py:
from django.shortcuts import render, redirect
from django.contrib import auth
from django.core.context_processors import csrf
from BearClubs.bc.forms.user import UserSignUpForm
from BearClubs.bc.models.organization import Organization
def directory(request):
first_50_clubs = [];
# get 50 clubs here
return render(request, 'directory.html' {'clubs': first_50_clubs});
I am really new to Django so pardon me. How do I go about getting the first 50 clubs in the first_50_clubs in the Organization.py view file?
According to the documentation, you can just use list slicing:
Use a subset of Python’s array-slicing syntax to limit your QuerySet
to a certain number of results. This is the equivalent of SQL’s LIMIT
and OFFSET clauses.
def directory(request):
first_50_clubs = Organization.objects.all()[:50]
return render(request, 'directory.html' {'clubs': first_50_clubs})
Also, you don't need to put semi-colons at the end of code lines in python.
Hope that helps.
You can get first 50 clubs in the first_50_clubs by following query :
first_50_clubs = Organization.objects.all().order_by('id')[:50]
It will fetch records when its got inserted .
If you want last inserted 50 records then just use - in order_by . like :
first_50_clubs = Organization.objects.all().order_by('-id')[:50]
I have a simple model MyModel with a date field named publication_date. I also have a custom manager that filters my model based on this date field.
This custom manager is accessible by .published and the default one by .objects.
from datetime import date, datetime
from django.db import models
class MyModelManager(models.Manager):
def get_query_set(self):
q = super(MyModelManager, self).get_query_set()
return q.filter(publication_date__lte=datetime.now())
class MyModel(models.Model):
...
publication_date = models.DateField(default=date.today())
objects = models.Manager()
published = MyModelManager()
This way, I got access to all objects in the admin but only to published ones in my views (using MyModel.published.all() queryset).
I also have
def get_previous(self):
return self.get_previous_by_publication_date()
def get_next(self):
return self.get_next_by_publication_date()
which I use in my templates: when viewing an object I can link to the previous and next object using
{{ object.get_previous }}
The problem is: this returns the previous object in the default queryset (objects) and not in my custom one (published).
I wonder how I can do to tell to this basic model functions (get_previous_by_FOO) to use my custom manager.
Or, if it's not possible, how to do the same thing with another solution.
Thanks in advance for any advice.
Edit
The view is called this way in my urlconf, using object_detail from the generic views.
(r'^(?P<slug>[\w-]+)$', object_detail,
{
'queryset': MyModel.published.all(),
'slug_field': 'slug',
},
'mymodel-detail'
),
I'm using Django 1.2.
In fact, get_next_or_previous_by_FIELD() Django function (which is used by get_previous_by_publication_date...) uses the default_manager.
So I have adapted it to reimplement my own utility function
def _own_get_next_or_previous_by_FIELD(self, field, is_next):
if not self.pk:
raise ValueError("get_next/get_previous cannot be used on unsaved objects.")
op = is_next and 'gt' or 'lt'
order = not is_next and '-' or ''
param = smart_str(getattr(self, field.attname))
q = Q(**{'%s__%s' % (field.name, op): param})
q = q|Q(**{field.name: param, 'pk__%s' % op: self.pk})
qs = MyModel.published.filter(q).order_by('%s%s' % (order, field.name), '%spk' % order)
try:
return qs[0]
except IndexError:
def get_previous(self):
return self._own_get_next_or_previous_by_FIELD(MyModel._meta.fields[4], False)
def get_next(self):
return self._own_get_next_or_previous_by_FIELD(MyModel._meta.fields[4], True)
This is not a very clean solution, as I need to hardcode the queryset and the field used, but at least it works.
I need help coming up with an efficient way to do a search query for a set of objects, based on a M2M field. My search form is going to look something like Blue Cross Blue Shield's | eg: this image
Now, let's suppose my model looks like this:
# models.py
class Provider(models.Model)
title = models.CharField(max_length=150)
phone = PhoneNumberField()
services_offered = models.ManyToManyField(ServiceType)
def __unicode__(self):
return self.title
class ServiceCategory(models.Model):
service_category = models.CharField(max_length=30)
def __unicode__(self):
return self.service_category
class Meta(object):
verbose_name_plural = "Service Categories"
class ServiceType(models.Model):
service_type = models.CharField(max_length=30)
service_category = models.ForeignKey(ServiceCategory)
def __unicode__(self):
return u'%s | %s' % (self.service_category, self.service_type
Also, we have to keep in mind that the options that we select are subject to change, since how they display on the form is dynamic (new ServiceCategories and ServiceTypes can be added at anytime). *How should I go about constructing a query for the Provider objects, given that a person using the search form can select multiple Services_Offered?*
This is currently my HIGHLY INEFFICIENT METHOD:
#managers.py
from health.providers.models import *
from django.db.models import Q
class Query:
def __init__(self):
self.provider_objects=Provider.objects.all()
self.provider_object=Provider.objects
self.service_object=ServiceType.objects
self.category_objects=ServiceCategory.objects.all()
def simple_search_Q(self, **kwargs): #matt's learning note: **kwargs passes any dictionary
return self.provider_objects.filter(
Q(services_offered__service_type__icontains=kwargs['service']),
Q(title__icontains=kwargs['title']),
Q(state=kwargs['state']),
).distinct().order_by('title')
====================
#views.py
from django.shortcuts import render_to_response
from health.providers.models import *
from health.search.forms import *
from health.search.managers import Query #location of the query sets
from django.core.paginator import Paginator, InvalidPage, EmptyPage
from django.template import RequestContext
def simple_search(request):
if request.method == 'POST':
SimpleSearch_form = SimpleSearch(request.POST)
if SimpleSearch_form.is_valid():
request.session["provider_list"] = None
kwargs = {'title': request.POST['title'],
'service': request.POST['service'], 'state': request.POST['state'] }
provider_list = Query().simple_search_Q(**kwargs)
return pagination_results(request, provider_list)
else:
SimpleSearch_form = SimpleSearch()
return render_to_response('../templates/index.html', { 'SimpleSearch_form': SimpleSearch_form},
context_instance=RequestContext(request))
How can I make my query:
Obtain Provider objects based on selecting multiple request.POST['service']
More efficient
Thanks for any help in advanced.
Best Regards,
Matt
1: for multiple request.POST['service'], I assume you mean these are CheckBoxes.
I'd make the CheckBox values ID's, not names, and do a PK lookup.
'services_offered__pk__in': request.POST.getlist('service')
That would return all Provider objects that have ALL of the services selected.
PS: You are also using CapitalCase for instances which is very confusing. If you want your code to be readable, I highly recommend some changes to your style (don't use CapitalCase for instances or variables) and make your variables more descriptive.
SimpleSearch_form = SimpleSearch() # what is SimpleSearch?
simplesearch_form = SimpleSearchForm() # now, it's very clear what the class SimpleSearchForm is
# and the form instance is clearly a for instance.
2: making it more efficient? You could get rid of a lot of code and code separation by remove your whole Query class. Also, I don't know why you are using Q objects since you are not doing anything that would require it (like OR or OR + AND).
def simple_search(request):
if request.method == 'POST':
searchform = SimpleSearchForm(request.POST)
if searchform.is_valid():
request.session['provider_list'] = None
post = request.POST
providers = Provider.objects.filter(services_offered__pk__in=post.getlist('services'),
title=post['title'], state=post['state'])
return pagination_results(request, provider_list)
else:
searchform = SimpleSearchForm()
return direct_to_template(request, '../templates/index.html', { 'searchform': searchform})