employee = Employee.objects.filter('age' = 99)
We assume this queryset is empty.
If I use employee[0], that will return index out of range error, so is it possible to have a None as default value here?
`employee[0] or None`? # This won't work, but this is what I mean.
If you just want to get a single instance, use get, not filter:
employee = Employee.objects.get(age = 99)
If that doesn't exist, you'll get a Employee.DoesNotExist exception, which you'll need to catch. If there's more than one 99 year-old employee, you'll get a Employee.MultipleObjectsReturned exception, which you may want to catch.
There's always django-annoying's get_object_or_None if you're feeling lazy!
from annoying.functions import get_object_or_None
obj = get_object_or_None(Employee, age=99)
If you don't want to use all of django-annoying, you can always add get_object_or_None somewhere, it just looks like this:
def get_object_or_None(klass, *args, **kwargs):
"""
Uses get() to return an object or None if the object does not exist.
klass may be a Model, Manager, or QuerySet object. All other passed
arguments and keyword arguments are used in the get() query.
Note: Like with get(), a MultipleObjectsReturned will be raised if
more than one object is found.
"""
queryset = _get_queryset(klass)
try:
return queryset.get(*args, **kwargs)
except queryset.model.DoesNotExist:
return None
On Django >= 1.6 there's the first method, so you can just do:
employee = Employee.objects.filter(age=99).first()
Note, however, that this only makes sense if you know the QuerySet is guaranteed to return either 1 or 0 results.
This should work:
employee[0] if employee else None
first_employee = employee[0] if employee.count() > 0 else None
Related
ive combined two models. one model's field is annotated to another model's so they can merge. However, when I try to return the data, I get TypeError: object of type 'Response' has no len(). I've followed several examples on stackoverflow and it doesnt seem to be working.
Here's what I have:
class ExploreAPIView(generics.ListAPIView):
def get_queryset(self):
merged_queryset = Place.get_queryset(self.request.user)
usr_pks = [u.pk for u in merged_queryset]
queryset = Place.objects.filter(pk__in=usr_pks)
serialUser = UserSerializer( User.objects.annotate(time=Extract('date_joined','epoch')), many=True).data[:]
serialPlace = PlacesSerializer(queryset, many=True).data[:]
chained_list = sorted(serialPlace +serialUser, key=lambda x: x.get('time'))
return Response(chained_list)
I dont understand why it returns no len() when it returns items if i print out the chained_list
You're returning a Response from get_queryset. But that method is supposed to return a queryset, as the name implies.
Assume i have the following function:
def select_queryset(value_to_decide_upon):
""" this function returns either any of some querysets or nothing """
if value_to_decide_upon == 1:
return User._default_manager.all()
elif value_to_decide_upon == 2:
return User._default_manager.filter(pk__in=[some_more_values])
elif value_to_decide_upon == n-1:
return User._default_manager.all().order_by('?')
elif value_to_decide_upon == n:
return None
Now here is the question: this function has a randomly ordered queryset as a return value:
queryset = User._default_manager.all().order_by('?')
Now the only important thing about that function is: it has to return the correct queryset. Is there a way to access the queryset in such way that i can, ie. do something like this:
class TestQuerysetSelection(TestCase):
def test_return_value(self):
# this, of course, will always fail:
self.assertEqual(select_queryset(n-1),
User._default_manager.all().order_by('?') )
# and this is not working as well
self.assertEqual( templatetag.queryset.order_by, '?' )
So how can i test if the function returns the correct queryset, without brute force comparing to all other querysets?
I think the most starighforward way is to compare sets (which are unordered).
self.assertEqual(
set(select_queryset(n-1)),
set(User._default_manager.all())
)
You could override order by and sort by pk, so you'll have order in your querysets and not random order. I'm not sure though what's the best way to compare querysets.
class TestQuerysetSelection(TestCase):
def test_return_value(self):
# Add `order_by('id')` to sort by id
self.assertEqual(select_queryset(n-1).order_by('id'),
User._default_manager.all().order_by('id') )
Try
qs1.query.sql_with_params() == qs2.query.sql_with_params()
# or simply
str(qs1.query) == str(qs2.query)
# for your code
self.assertEqual(select_queryset(n-1).query.sql_with_params(),
User._default_manager.all().order_by('?').query.sql_with_params())
Furthermore, I think you need a more concise function to return these querysets.
I've got a line in python:
streams = Stream.objects.filter(info__isnull = False)\
.order_by('-score', 'info__title')
I feed that to a RequestContext
c = RequestContext(request, {
'online_streams': streams.filter(online = True),
'offline_streams': streams.filter(online = False)
})
It sorts a table of ~50 rows by their score and title. This is good, but there is one row I would like to always be top. Simply fetching it beforehand and filtering it out of the line above, then give it to the RequestContext separately won't work, since I'd have to modify the template. Also I might change which row should be on top later, or perhaps make it multiple rows instead.
Is there a filter argument I can insert that says something in the lines of "Order all rows by score then title, except row ID 8, which should always be top"? Or could I perhaps change the order of the QuerySet manually after the filtering?
Thanks for any suggestions
You can override Model manager method:
class MyManager(models.Manager):
def filter(self, *args, **kwargs):
return super(MyManager, self).filter(*args, **kwargs).exclude(id=8).order_by(...)
then in model
class Stream(models.Model):
...
objects = MyManager()
EDIT
to make sure it is included you can query:
from django.db.models import Q
return super(MyManager, self).filter(Q(id=8) | Q(*args, **kwargs)).order_by(...)
I couldn't find a way to use query sets to do this, so I ended up converting the query set to a list and sorting it using the magic compare function. It ended up looking something like this:
The magic sorter, checking for names that should be stuck "on top":
# Comparison magic
def __cmp__(self, other):
# On top
if self.name in stream_ontop:
return 1
if other.name in stream_ontop:
return -1
# Score
if self.score > other.score:
return 1
if self.score < other.score:
return -1
# Title alphabetical
if self.info.title > other.info.title:
return -1
if self.info.title < other.info.title:
return 1
return 0
Then I fetched and sorted them like this from my model class
# Custom sorting
#staticmethod
def getAllSorted(online):
streams = list(Stream.objects.filter(online=online, info__isnull=False))
streams.sort(key=None, reverse=True)
return streams
from django.core.exceptions import FieldError
#This is a method of a class
def _order_item_list(self, item_list, order_items_by, previous_order_by):
if order_items_by == previous_order_by:
order_items_by = '-' + order_items_by
try:
result = item_list.order_by(order_items_by)
except FieldError:
result = item_list
return result, order_items_by
Now when I order by valid fields following the generated link,everything works perfect. When I edit a link and add some dummy fieldnames for ordering, it should be catched by this exception and the original list should be returned. But it is not happening, instead I always get a FieldError from django.
FieldError at ...
Cannot resolve keyword u'fgsdffds' into field. Choices are: ...
The reason the exception is not caught is because the QuerySet has not been evaluated yet.
To validate an arbitrary (user-specified) value used for a model field or order_by value, simply check to see if that model has a field by that name.
For example, say you have a model called Ticket and an arbitrary GET parameter called field_name. Here's how you might handle creating a valid QuerySet in views.py:
from django.db.models import FieldDoesNotExist
from myapp.models import Ticket
def index(request):
default_field = 'id'
field_name = request.GET.get('field_name', default_field)
try:
Ticket._meta.get_field_by_name(field_name)
except FieldDoesNotExist:
field_name = default_field
tickets = Ticket.objects.all().order_by(field_name)
return ...
This means there's a typo, or the exception happens elsewhere. Insert a debug line:
import pdb; pdb.set_trace()
before the try-except and see how the code is executed. Try PUDB or IPDB debuggers instead of the standard one. Many questions disappear when you have a debugger and can see exactly what goes wrong.
I faced the same problem and surely it was because the exception is later. In order to raise exception in try-catch block I modified the code in following manner:
try:
result = item_list.order_by(order_items_by)
**result = list(result)**
except FieldError:
result = item_list
This worked for me.
In Django, queryset provides a method called get_or_create that either returns an objects or creates an object.
However, like the get method, get_or_create can throw an exception if the query returns multiple objects.
Is there an method to do this elegantly:
objects = Model.manager.filter(params)
if len(objects) == 0:
obj = Model.objects.create(params)
else:
obj = objects[0]
get_or_create() is just a convenience function so there's nothing wrong with writing your own, like pavid has shown or
result = Model.objects.filter(field__lookup=value)[0]
if not result:
result = Model.objects.create(...)
return result
EDIT
As suggested, changed the [:1] slice (which returns a single-entry list) after the filter to [0] (which returns the actual object). The problem with this is it will raise an exception if there is not match to the query.
This will also raise a simliar exception:
Model.objects.filter(field_lookup=value).latest()
Looking at the question again, I'm not sure whether the original poster is looking to return multiple objects/rows, or just a way to get around raising an exception when retrieving a single object/row.
Here's another option?
results = Model.objects.filter(...)
if results.exists():
return results
else:
return Model.objects.create(...)
and another:
result = None
try:
result = Model.objects.get(...)
except Model.DoesNotExist:
result = Model.objects.create(...)
There's nothing wrong with raising & catching exceptions!
From django 1.6 there is a convenience method first() that returns the first result in a filter query, or None.
obj = Model.manager.filter(params).first()
if obj is None:
obj = Model.objects.create(params)
Here's a manager method that will allow you to extend this function elegantly
class FilterOrCreateManager(models.Manager):
"""Adds filter_or_create method to objects
"""
def filter_or_create(self, **kwargs):
try:
created = False
obj = self.filter(**kwargs).first()
if obj is None:
obj = self.create(**kwargs)
created = True
return (obj,created)
except Exception as e:
print(e)
Then ensure you add the manager to whichever model(s) you want to use this on:
class MyObj(models.Model):
objects = FilterOrCreateManager()
After that, you will be able to use it as you would get_or_create:
obj_instance, created = MyObj.filter_or_create(somearg='some value')
I just came across this question and wanted to contribute because no one has suggested actually catching the IndexError.
try:
obj = Model.objects.filter(params)[0]
except IndexError:
obj = Model.objects.create(params)
Do it in one line:
obj = Model.objects.filter(params).first() or Model.objects.create(params)
You can try this:
result = Model.objects.filter(field__lookup=value)[0]
if not result:
result = Model.objects.create(...)
return result
This works for me:
In your view, call something like this:
obj = Category.objects.get_or_create_category(form.cleaned_data.get('name'))
In your Model manager, create a function like this:
class CategoryManager(models.Manager):
def get_or_create_category(self, query):
try:
result = Category.objects.filter(name = query)[0]
except:
result = Category.objects.create(name = query)
return result
The logic is simple. First, try to retrieve the first Category object who's name matches the query string (which is provided by the form). If the retrieval fails (because it doesn't exist), create a new Category with the string as its name. Return the result for use in your view.