quick question. Does anyone have any idea how to write conditionals in django models?
For example I have this code here:
class Trip(models.Model):
tripName = models.CharField(max_length=64)
tripLogo = models.ImageField(default='default_trip.jpg', upload_to='trip_pics')
So here default value is 'default_trip.jpg', but I'd like to write a conditional that if tripName == "russian" than default=russia.jpg. Maybe not change default, but another image will be initiated.
This is not something that can be done on the model level, it must be done in the controller (otherwise, this would break the MVC pattern).
Keep in mind that Django's ORM wrapper must turn your model class into a usable table in whatever the underlaying database engine is. This type of "conditional default" is not part of any database engine that I know of.
default arg can be a calable.
def contact_default():
return {"email": "to1#example.com"}
contact_info = JSONField("ContactInfo", default=contact_default)
read this
So this part of code helped me to solve my problem.
def save(self, *args, **kwargs):
tripName = getattr(self, 'tripName')
if tripName in tripImages:
self.tripLogo = "{}.png".format(tripName.lower())
else:
self.tripLogo = "default_trip.png"
Related
I have been struggling all morning trying to figure out how to compare two different querysets. I have two manytomanyfields in my model, and I'm trying to figure out if they are identical.
I research this by viewing this particular issue: How do I test Django QuerySets are equal?
I am using class based views...and I have a model with two manytomanyfields...
My model...
class Author(models.Model):
title = models.ManyToManyField(User,blank=True,related_name='title')
title1 = models.ManyToManyField(User,blank=True,related_name='title1)
My View...
class AuthorDetailView(DetailView):
model = Author
def get_context_data(self, **kwargs):
context = super(AuthorDetailView, self).get_context_data(**kwargs)
title = list(Author.objects.filter(title))
title1 = list(Author.objects.filter(title1))
test_instance = Author.objects.all()
proxy4 = self.assertQuerySetEqual(Author.objects.all(), map(repr, [test_instance]))
I am trying to compare fields title and title1. However when try to run the code above I continually get a 'View' object has no attribute 'assertQuerysetEqual'. I can't even get this function to work. I running Django 1.11 and Postgresql. Perhaps this function doesn't work with Postgresql? Any help to get me on the right track is appreciated. Been playing with this and researching all morning with no luck. Thanks in advance.
Update...I have also been playing with various versions of trying to compare title_set.all and title1_set.all in the views....this is working intermittently...but the two are always returning that they are equal.
self.assertQuerySetEqual is about unit-testing.
All you need is compare two lists in your view. It can be done with sets.
set(title) == set(title1)
Your AuthorDetailView need to inherits from django.test.TestCase in order to get access to self.assertQuerySetEqual which is a testing feature.
So basically you need to do like this example:
from django.test import TestCase
...
class AuthorDetailView(DetailView, TestCase):
...
Otherwise for comparing two lists you can use set() like what #Daniil Mashkin said in his answer.
So, more in depth, you can do something like this:
class AuthorDetailView(DetailView):
model = Author
def get_context_data(self, **kwargs):
context = super(AuthorDetailView, self).get_context_data(**kwargs)
title = list(Author.objects.all().values_list('title'))
title1 = list(Author.objects.all().values_list('title1'))
# test if the two lists are equal
equal_ = set(title) == set(title1)
# add the result to the context variable
context.update({'titles_are_equal': equal_})
# return it in order to get the variable `title_are_equal`
# into your template
return context
For more informations visit the django official documentaiton
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,
Below is a stripped down model and associated method. I am looking for a simple way upon executing a query to get all of the needed information in a single answer without having to re-query everything. The challenge here is the value is dependent upon the signedness of value_id.
class Property(models.Model):
property_definition = models.ForeignKey(PropertyDefinition)
owner = models.IntegerField()
value_id = models.IntegerField()
def get_value(self):
if self.value_id < 0: return PropertyLong.objects.get(id=-self.value_id)
else: return PropertyShort.objects.get(id=self.value_id)
Right now to get the "value" I need to do this:
object = Property.objects.get(property_definition__name="foo")
print object.get_value()
Can someone provide a cleaner way to solve this or is it "good" enough? Ideally I would like to simply just do this.
object = Property.objects.get(property_definition__name="foo")
object.value
Thanks
Given this is a bad design. You can use the builtin property decorator for your method to make it act as a property.
class Property(models.Model):
property_definition = models.ForeignKey(PropertyDefinition)
owner = models.IntegerField()
value_id = models.IntegerField()
#property
def value(self):
if self.value_id < 0: return PropertyLong.objects.get(id=-self.value_id)
else: return PropertyShort.objects.get(id=self.value_id)
This would enable you to do what you'd ideally like to do: Property.objects.get(pk=1).value
But I would go as far as to call this "cleaner". ;-)
You could go further and write your own custom model field by extending django.models.Field to hide the nastiness in your schema behind an API. This would at least give you the API you want now, so you can migrate the nastiness out later.
That or the Generic Keys mentioned by others. Choose your poison...
this is a bad design. as Daniel Roseman said, take a look at generic foreign keys if you must reference two different models from the same field.
https://docs.djangoproject.com/en/1.3/ref/contrib/contenttypes/#generic-relations
Model inheritance could be used since value is not a Field instance.
is there something like getters and setters for django model's fields?
For example, I have a text field in which i need to make a string replace before it get saved (in the admin panel, for both insert and update operations) and make another, different replace each time it is read. Those string replace are dynamic and need to be done at the moment of saving and reading.
As I'm using python 2.5, I cannot use python 2.6 getters / setters.
Any help?
You can also override setattr and getattr. For example, say you wanted to mark a field dirty, you might have something like this:
class MyModel:
_name_dirty = False
name = models.TextField()
def __setattr__(self, attrname, val):
super(MyModel, self).__setattr__(attrname, val)
self._name_dirty = (attrname == 'name')
def __getattr__(self, attrname):
if attrname == 'name' and self._name_dirty:
raise('You should get a clean copy or save this object.')
return super(MyModel, self).__getattr__(attrname)
You can add a pre_save signal handler to the Model you want to save which updates the values before they get saved to the database.
It's not quite the same as a setter function since the values will remain in their incorrect format until the value is saved. If that's an acceptable compromise for your situation then signals are the easiest way to achieve this without working around Django's ORM.
Edit:
In your situation standard Python properties are probably the way to go with this. There's a long standing ticket to add proper getter/setter support to Django but it's not a simple issue to resolve.
You can add the property fields to the admin using the techniques in this blog post
Overriding setattr is a good solution except that this can cause problems initializing the ORM object from the DB. However, there is a trick to get around this, and it's universal.
class MyModel(models.Model):
foo = models.CharField(max_length = 20)
bar = models.CharField(max_length = 20)
def __setattr__(self, attrname, val):
setter_func = 'setter_' + attrname
if attrname in self.__dict__ and callable(getattr(self, setter_func, None)):
super(MyModel, self).__setattr__(attrname, getattr(self, setter_func)(val))
else:
super(MyModel, self).__setattr__(attrname, val)
def setter_foo(self, val):
return val.upper()
The secret is 'attrname in self.__dict__'. When the model initializes either from new or hydrated from the __dict__!
While I was researching the problem, I came across the solution with property decorator.
For example, if you have
class MyClass(models.Model):
my_date = models.DateField()
you can turn it into
class MyClass(models.Model):
_my_date = models.DateField(
db_column="my_date", # allows to avoid migrating to a different column
)
#property
def my_date(self):
return self._my_date
#my_date.setter
def my_date(self, value):
if value > datetime.date.today():
logger.warning("The date chosen was in the future.")
self._my_date = value
and avoid any migrations.
Source: https://www.stavros.io/posts/how-replace-django-model-field-property/
Now I have this code:
class Mymodel(models.Model):
xxxx_count = ForeignCountField(filter={'foreign_table__xxxx': True})
yyyy_count = ForeignCountField(filter={'foreign_table__yyyy': True})
zzzz_count = ForeignCountField(filter={'foreign_table__zzzz': True})
qqqq_count = ForeignCountField(filter={'foreign_table__qqqq': True})
ssss_count = ForeignCountField(filter={'foreign_table__ssss': True})
rrrr_count = ForeignCountField(filter={'foreign_table__rrrr': True})
I want something like this:
class Mymodel(models.Model):
for code in ['xxxx','yyyy','zzzz','qqqq','ssss','rrrr']:
setattr(self, '%s_count' % code, ForeignCountField(filter={'foreign_table__%s' % code: True}))
But when I try to do this, error raised: "self doesn't defined". Do I need to put this code into some other place?
If you want to add fields outside model definition you have to use add_to_class model method:
class Mymodel(models.Model):
pass
for code in ['xxxx','yyyy','zzzz','qqqq','ssss','rrrr']:
Mymodel.add_to_class('%s_count' % code, ForeignCountField(filter={'foreign_table__%s' % code: True})))
The way to automate the creation of multiple class attributes is by writing a metaclass. It'd certainly be possible to subclass Django's ModelBase metaclass and add the functionality you need; but it would also be overkill and probably less maintainable than just writing them out.
I'd like to know a bit more about the design decisions that got you to this point. What is a ForeignCountField and why do you need it? Shouldn't it be a property of the respective tables? (a method on the manager or a classmethod?)
Maybe you need to put you're code in the constructor method init of your class.
def __init__(self, *args, **kwargs):
.....
super(MyModel, self).__init__(*args, **kwargs)
Edit: Sorry this don't work. Maybe you can try looking at This, 'this works with django 0.96. There are some modification that make it hard to adjust it with django version 1.0'.
if you wan't it Dirty and Quick you can try something like this:
class Foo(models.Model):
letters = ["A", "B", "C"]
for l in letters:
exec "%s_count = models.CharField(max_length=255)" % l