I'm trying to populate a list based on partiular members, but it doesn't seem to be going to plan.
user = request.user
members = []
userprofile = user.get_profile()
if user.is_superuser or user.is_staff:
if userprofile.countries.count() == 0:
members = Member.objects.all()
elif userprofile.countries.count() >0:
for c in userprofile.countries.all():
m1 = Member.objects.filter(location__country = c)
members.append(m1)
else:
pass
self.fields['members'].choices = [(member.id, member.display_name()) for member in members]
Here, we see self.fields (it's a multi-select box) has member.id. I've tried both this and member.pk, but it doesn't seem to be working => Django informs me member has no attributes called id or pk
If a user is a superuser and has a countries count of 0, then It works fine; So i know its to do with the append function underneath the queryset call.
Could anyone offer any hints as to why id/pk is unavailable/lost after adding the results to a list? In addition, does anyone know of a workaround?
I think your problem is that Member.objects.filter(location__country = c) is going to return a QuerySet object... which is essentially a hook to execute a query (lazily) once it is evaluated. if you change that to list(Member.objects.filter(location__country = c)) it will be evaluated immediately and give you back a list of Member model instances instead of a QuerySet object.
There are a couple problems with your code. First, Member.objects.filter(location__country=c) returns a queryset. The queryset is not evaluated by being appended to members (see https://docs.djangoproject.com/en/1.3/ref/models/querysets/#when-querysets-are-evaluated). Second, even if you evaluate the queryset as Matthew suggests, members will be a list of lists, instead of a flat list as your code expects. Try this code instead:
if user.is_superuser or user.is_staff:
if not userprofile.countries.count():
members = Member.objects.all()
else:
members = Member.objects.filter(location__country__in=userprofile.countries.all())
Related
While creating a front end for a Django module I faced the following problem inside Django core:
In order to display a link to the next/previous object from a model query, we can use the extra-instance-methods of a model instance: get_next_by_FIELD() or get_previous_by_FIELD(). Where FIELD is a model field of type DateField or DateTimeField.
Lets explain it with an example
from django.db import models
class Shoe(models.Model):
created = models.DateTimeField(auto_now_add=True, null=False)
size = models.IntegerField()
A view to display a list of shoes, excluding those where size equals 4:
def list_shoes(request):
shoes = Shoe.objects.exclude(size=4)
return render_to_response(request, {
'shoes': shoes
})
And let the following be a view to display one shoe and the corresponding
link to the previous and next shoe.
def show_shoe(request, shoe_id):
shoe = Shoe.objects.get(pk=shoe_id)
prev_shoe = shoe.get_previous_by_created()
next_shoe = shoe.get_next_by_created()
return render_to_response('show_shoe.html', {
'shoe': shoe,
'prev_shoe': prev_shoe,
'next_shoe': next_shoe
})
Now I have the situation that the show_shoe view displays the link to the previous/next regardless of the shoes size. But I actually wanted just shoes whose size is not 4.
Therefore I tried to use the **kwargs argument of the get_(previous|next)_by_created() methods to filter out the unwanted shoes, as stated by the documentation:
Both of these methods will perform their queries using the default manager for the model. If you need to emulate filtering used by a custom manager, or want to perform one-off custom filtering, both methods also accept
optional keyword arguments, which should be in the format described in Field lookups.
Edit: Keep an eye on the word "should", because then also (size_ne=4) should work, but it doesn't.
The actual problem
Filtering using the lookup size__ne ...
def show_shoe(request, shoe_id):
...
prev_shoe = shoe.get_previous_by_created(size__ne=4)
next_shoe = shoe.get_next_by_created(size__ne=4)
...
... didn't work, it throws FieldError: Cannot resolve keyword 'size_ne' into field.
Then I tried to use a negated complex lookup using Q objects:
from django.db.models import Q
def show_shoe(request, shoe_id):
...
prev_shoe = shoe.get_previous_by_created(~Q(size=4))
next_shoe = shoe.get_next_by_created(~Q(size=4))
...
... didn't work either, throws TypeError: _get_next_or_previous_by_FIELD() got multiple values for argument 'field'
Because the get_(previous|next)_by_created methods only accept **kwargs.
The actual solution
Since these instance methods use the _get_next_or_previous_by_FIELD(self, field, is_next, **kwargs) I changed it to accept positional arguments using *args and passed them to the filter, like the **kwargs.
def my_get_next_or_previous_by_FIELD(self, field, is_next, *args, **kwargs):
"""
Workaround to call get_next_or_previous_by_FIELD by using complext lookup queries using
Djangos Q Class. The only difference between this version and original version is that
positional arguments are also passed to the filter function.
"""
if not self.pk:
raise ValueError("get_next/get_previous cannot be used on unsaved objects.")
op = 'gt' if is_next else 'lt'
order = '' if is_next else '-'
param = force_text(getattr(self, field.attname))
q = Q(**{'%s__%s' % (field.name, op): param})
q = q | Q(**{field.name: param, 'pk__%s' % op: self.pk})
qs = self.__class__._default_manager.using(self._state.db).filter(*args, **kwargs).filter(q).order_by('%s%s' % (order, field.name), '%spk' % order)
try:
return qs[0]
except IndexError:
raise self.DoesNotExist("%s matching query does not exist." % self.__class__._meta.object_name)
And calling it like:
...
prev_shoe = shoe.my_get_next_or_previous_by_FIELD(Shoe._meta.get_field('created'), False, ~Q(state=4))
next_shoe = shoe.my_get_next_or_previous_by_FIELD(Shoe._meta.get_field('created'), True, ~Q(state=4))
...
finally did it.
Now the question to you
Is there an easier way to handle this? Should shoe.get_previous_by_created(size__ne=4) work as expected or should I report this issue to the Django guys, in the hope they'll accept my _get_next_or_previous_by_FIELD() fix?
Environment: Django 1.7, haven't tested it on 1.9 yet, but the code for _get_next_or_previous_by_FIELD() stayed the same.
Edit: It is true that complex lookups using Q object is not part of "field lookups", it's more part of the filter() and exclude() functions instead. And I am probably wrong when I suppose that get_next_by_FIELD should accept Q objects too. But since the changes involved are minimal and the advantage to use Q object is high, I think these changes should get upstream.
tags: django, complex-lookup, query, get_next_by_FIELD, get_previous_by_FIELD
(listing tags here, because I don't have enough reputations.)
You can create custom lookup ne and use it:
.get_next_by_created(size__ne=4)
I suspect the method you've tried first only takes lookup arg for the field you're basing the get_next on. Meaning you won't be able to access the size field from the get_next_by_created() method, for example.
Edit : your method is by far more efficient, but to answer your question on the Django issue, I think everything is working the way it is supposed to. You could offer an additional method such as yours but the existing get_next_by_FIELD is working as described in the docs.
You've managed to work around this with a working method, which is OK I guess, but if you wanted to reduce the overhead, you could try a simple loop :
def get_next_by_field_filtered(obj, field=None, **kwargs):
next_obj = getattr(obj, 'get_next_by_{}'.format(field))()
for key in kwargs:
if not getattr(next_obj, str(key)) == kwargs[str(key)]:
return get_next_by_field_filtered(next_obj, field=field, **kwargs)
return next_obj
This isn't very efficient but it's one way to do what you want.
Hope this helps !
Regards,
I have a boolean field on my model that represents whether someone has canceled their membership or not. I am trying to create a custom SimpleListFilter that allows this field to be filtered on.
However, I really want to show only those who are not canceled by default. Is there someway to select the "No" option by default? This is my filter so far:
class CanceledFilter(SimpleListFilter):
title = 'Canceled'
# Parameter for the filter that will be used in the URL query.
parameter_name = 'canceled'
def lookups(self, request, model_admin):
return (
(True, 'Yes'),
(False, 'No'),
)
def queryset(self, request, queryset):
if self.value() is True or self.value() is None:
return queryset.filter(canceled=True)
if self.value() is False:
return queryset.filter(canceled=False)
EDIT:
I should have been a bit clearer. I am specifically trying to do this in the Admin interface. When I add the above filter as a list_filter in admin. I get a filter on the side of the admin page with 3 choices: All, Yes and No.
I would like the "No" choice or none of the choices to be set by default. Instead the "All" choice is always set by default. Is there some none hacky way to set the default filter choice or something like that.
Basiclly in Admin when they view the Members, I only want to show the active (not canceled) by default. If they click "All" or "Yes" then I want to show the canceled ones.
Update:
Note this is the same as question Default filter in Django admin, but I that question is now 6 years old. The accepted answer is marked as requiring Django 1.4. I am not sure if that answer will still work with newer Django versions or is still the best answer.
Given the age of the answers on the other question, I am not sure how we should proceed. I don't think there is any way to merge the two.
Had to do the same and stumbled upon your question. This is how I fixed it in my code (adapted to your example):
class CanceledFilter(SimpleListFilter):
title = 'Canceled'
# Parameter for the filter that will be used in the URL query.
parameter_name = 'canceled'
def lookups(self, request, model_admin):
return (
(2, 'All'),
(1, 'Yes'),
(0, 'No'),
)
def queryset(self, request, queryset):
if self.value() is None:
self.used_parameters[self.parameter_name] = 0
else:
self.used_parameters[self.parameter_name] = int(self.value())
if self.value() == 2:
return queryset
return queryset.filter(cancelled=self.value())
Some explanation is required. The querystring is just part of the URL, and exactly what the name implies: a query string. Your values come in as strings, not as booleans or integers. So when you call self.value(), it returns a string.
If you examine the URL you get when you click on the Yes/No, when not using a custom list filter, you'll see it encodes it as 1/0, not True/False. I went with the same scheme.
For completeness and our future readers, I also added 2 for All. Without verifying, I assume that was None before. But None is also used when nothing is selected, which defaults to All. Except, in our case it needs to default to False, so I had to pick a different value. If you don't need the All option, just remove the final if-block in the queryset method, and the first tuple in the lookups method.
With that out of the way, how does it work? The trick is in realising that self.value() just returns:
self.used_parameters.get(self.parameter_name, None)
which is either a string, or None, depending on whether the key is found in the dictionary or not. So that's the central idea: we make sure it contains integers and not strings, so that self.value() can be used in the call to queryset.filter(). Special treatment for the value for All, which is 2: in this case, just return queryset rather than a filtered queryset. Another special value is None, which means there is no key parameter_name in the dictionary. In that case, we create one with value 0, so that False becomes the default value.
Note: your logic was incorrect there; you want the non-cancelled by default, but you treat None the same as True. My version corrects this.
ps: yes, you could check for 'True' and 'False' rather than True and False in your querystring method, but then you'd notice the correct selection would not be highlighted because the first elements in your tuple don't match up (you're comparing strings to booleans then). I tried making the first elements in the tuples strings too, but then I'd have to do string comparison or eval to match up 'True' to True, which is kind of ugly/unsafe. So best stick to integers, like in my example.
If anyone is still interested in a solution for this, I used a different and IMHO much cleaner approach. As I'm fine with a default choice and the handling of it, I decided I just want to rename the default display label. This is IMHO much cleaner and you don't need any "hacks" to handle the default value.
class CompleteFilter(admin.SimpleListFilter):
'''
Model admin filter to filter orders for their completion state.
'''
title = _('completion')
parameter_name = 'complete'
def choices(self, changelist):
'''
Return the available choices, while setting a new default.
:return: Available choices
:rtype: list
'''
choices = list(super().choices(changelist))
choices[0]['display'] = _('Only complete')
return choices
def lookups(self, request, model_admin):
'''
Return the optionally available lookup items.
:param django.http.HttpRequest request: The Django request instance
:param django.contrib.admin.ModelAdmin model_admin: The model admin instance
:return: Optional lookup states
:rtype: tuple
'''
return (
('incomplete', _('Only incomplete')),
('all', _('All')),
)
def queryset(self, request, queryset):
'''
Filter the retreived queryset.
:param django.http.HttpRequest request: The Django request instance
:param django.db.models.query.QuerySet: The Django database query set
:return: The filtered queryset
:rtype: django.db.models.query.QuerySet
'''
value = self.value()
if value is None:
return queryset.filter(state__complete=True)
elif value == 'incomplete':
return queryset.filter(state__complete=False)
return queryset
In the choices() method, I just rename the display label from All to Only complete. Thus, the new default (which has a value of None is now renamed).
Then I've added all additional lookups as usual in the lookups() method. Because I still want an All choice, I add it again. However, you can also skip that part if you don't need it.
That's basically it! However, if you want to display the All choice on top again, you might want to reorder the choices list in the choices() method before returning it. For example:
# Reorder choices so that our custom "All" choice is on top again.
return [choices[2], choices[0], choices[1]]
Look at the section called "Adding Extra Manager Methods" in the link below:
http://www.djangobook.com/en/2.0/chapter10.html
You can add an additional models.Manager to your model to only return people that have not cancelled their membership. A rough implementation of the additional models.Manager would look like this:
class MemberManager(models.Manager):
def get_query_set(self):
return super(MemberManager, self).get_query_set().filter(membership=True)
class Customer(models.Model):
# fields in your model
membership = BooleanField() # here you can set to default=True or default=False for when they sign up inside the brackets
objects = models.Manager # default Manager
members = MemberManager() # Manager to filter for members only
Anytime you need to get a list of you current members only, you would then just call:
Customer.members.all()
Suppose I have a Person model that has a first name field and a last name field. There will be many people who have the same first name. I want to write a TastyPie resource that allows me to get a list of the unique first names (without duplicates).
Using the Django model directly, you can do this easily by saying something like Person.objects.values("first_name").distinct(). How do I achieve the same thing with TastyPie?
Update
I've adapted the apply_filters method linked below to use the values before making the distinct call.
def apply_filters(self, request, applicable_filters):
qs = self.get_object_list(request).filter(**applicable_filters)
values = request.GET.get('values', '').split(',')
if values:
qs = qs.values(*values)
distinct = request.GET.get('distinct', False) == 'True'
if distinct:
qs = qs.distinct()
return qs
values returns dictionaries instead of model objects, so I don't think you need to override alter_list_data_to_serialize.
Original response
There is a nice solution to the distinct part of the problem here involving a light override of apply_filters.
I'm surprised I'm not seeing a slick way to filter which fields are returned, but you could implement that by overriding alter_list_data_to_serialize and deleting unwanted fields off the objects just before serialization.
def alter_list_data_to_serialize(self, request, data):
data = super(PersonResource, self).alter_list_data_to_serialize(request, data)
fields = request.GET.get('fields', None)
if fields is not None:
fields = fields.split(',')
# Data might be a bundle here. If so, operate on data.objects instead.
data = [
dict((k,v) for k,v in d.items() if k in fields)
for d in data
]
return data
Combine those two to use something like /api/v1/person/?distinct=True&values=first_name to get what you're after. That would work generally and would still work with additional filtering (&last_name=Jones).
I have a third-party function which gives me a filtered queryset (e.g. records with 'valid'=True) but I want to remove a particular condition (e.g. to have all records, both valid and invalid).
Is there a way to remove a filter condition to an already-filtered queryset?
E.g.
only_valid = MyModel.objects.filter(valid=True)
all_records = only_valid.**remove_filter**('valid')
(I know that it would be better to define 'all_records' before 'only_valid', but this is just an example...)
Although there is no official way to do this using filter notation, you may easily do it with Q-notation.
For example, if you ensure that third-part function returns a Q object, not a filtered QuerySet, you may do the following:
q = ThirdParty()
q = q | Q(valid=False)
And the resulting SQL conditions will be joined using OR operator.
From the docs:
Each time you refine a QuerySet, you get a brand-new QuerySet that is in no way bound to the previous QuerySet. Each refinement creates a separate and distinct QuerySet that can be stored, used and reused.
I doubt therefore, that there is a standard way to do it. You could dig into the code, see, what filter() does and try a bit. If that doesn't help, my assumption is, you're out of luck and need to re-build the query yourself.
Use this function
from django.db.models import Q
def remove_filter(lookup, queryset):
"""
Remove filter lookup in queryset
```
>>> queryset = User.objects.filter(email='user#gmail.com')
>>> queryset.count()
1
>>> remove_filter('email', queryset)
>>> queryset.count()
1000
```
"""
query = queryset.query
q = Q(**{lookup: None})
clause, _ = query._add_q(q, self.used_aliases)
def filter_lookups(child):
return child.lhs.target != clause.children[0].lhs.target
query.where.children = list(filter(filter_lookups, query.where.children))
Here's what I did in a similar case.
all_records = MyModel.objects.all()
only_valid = MyModel.objects.filter(valid=True)
only_valid.original = all_records
...
all_records = only_valid.original
Obviously this clears any other filters too so it won't be right for every case.
original_query_set = MyModel.objects.filter(**conditions)
model_class = orginal_query_set.model
new_query_set = model_class.objects.filter(**new_conditions)
You can use the .model attribute on a QuerySet to get the model class, then use the model class to make a brand new QuerySet.
Thanks for making me check the source code Boldewyn. So, it seems there's a new method right below filter() with the same parameters... called exclude() (as of commit mini-hash ef6c680, for when it loses its line number)
Return a new QuerySet instance with NOT (args) ANDed to the existing set.
So, to answer the original question:
only_valid = MyModel.objects.filter(valid=True)
filtered_results = only_valid.exclude(the_condition_to_remove=True)
I am making some view functions to calculate the rank of one user in a community.
My problem is that i want to display the rank afferent to each user, in its profile, and i don't know how, since i don;t have a request and a render_to_response (cause i guessed they are not needed)
my code:
def calculate_questions_vote(request):
useranswer = Answer.objects.filter (answer_by = request.user)
positive_votes = VoteUpAnswer.objects.filter(answer = useranswer)
negative_votes = VoteDownAnswer.objects.filter(answer = useranswer)
question_vote_rank = sum(positive_votes) - sum(negative_votes.count)
return question_vote_rank
def calculate_replies(request):
the_new = News.objects.filter(created_by = request.user)
reply = Reply.objects.filter(reply_to = the_new)
reply_rank = sum(reply)
return reply_rank
def calculate_votes(request):
the_new = News.objects.filter(created_by = request.user)
vote = Vote.objects.filter(voted = the_new)
vote_rank = sum(vote)
return vote_rank
def personal_rank(request):
personal_rank = question_vote_rank + reply_rank + vote_rank
return personal_rank
and in UserProfiles:
user = request.user
personal_rank = calculate_questions_vote(user) + calculate_replies(user) + personal_rank(user)
but my error is:
Error binding parameter 0 - probably unsupported type.
Is mt approach correct? How should i call the rank function in the profile_view def ?
Thanks!
YOu can call the function in your view like rank = personal_rank(reuest.user) and add rank to your context then.
I wouldnt call this functions "view"-functions, since they are not dealing with a request nor are they returning a HttoResponce; they are more "helper" functions and also don't belong to a model if they are dealing with seperate entities (eg. News & Vote). A descent place for them would be eg. utils.py. You import them from there in your views.py and call them with the user as parameter (if it's the actual user it's request.user).
It makes sense that you can't access request from everywhere, because this forces you to keep more to a mvc-like design, if you need request somewhere you need to pass it on from your original view function!
You should change your last function to:
def personal_rank(user):
personal_rank = calculate_questions_vote(user) + \
calculate_replies(user) + \
calculate_votes(user)
return personl_rank
You could also add this last function to your User or UserProfile model class if you have something like that and then call eg. my_user.personal_rank() or my_user.get_profile().personal_rank().
lazerscience gave good explanation for your guestion. I just want to point some possible performance issues.
Please note that calling this method for each User yields with 7 queries. If your case will be showing rank for all users it might take a lot of time. I suggest make some caching (like in StackOverflow). There are many options here, for example you can assign special rank field to a User model (or UserProfile if User is auth.User) in which you could store precalaculated rank (calculated just after some operation, like News saving or in some scheduled intervals).