Is it possible to define names of model's fields dynamically? - django

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

Related

How can I use a related field in a SlugRelatedField?

I have the following structures
class State(models.Model):
label = models.CharField(max_length=128)
....
class ReviewState(models.Model):
state = models.ForeignKey(State, on_delete=models.CASCADE)
...
class MySerializer(serializers.HyperlinkedModelSerializer):
state = serializers.SlugRelatedField(queryset=ReviewState.objects.all(), slug_field='state__label', required=False)
class Meta:
model = MyModel
fields = [
'id',
'state', # this points to a ReviewState object
....
]
What I'm trying to do is using the State object's label as the field instead. But it doesn't seem like djangorestframework likes the idea of using __ to lookup slug fields. Would it be possible to do this? If it was:
class MySerializer(serializers.HyperlinkedModelSerializer):
state = serializers.SlugRelatedField(queryset=State.objects.all(), slug_field='label', required=False)
that would be no problem, but I'm trying to use the ReviewState instead. I'm also trying to avoid having a ReviewStateSerializer as the resulting json would look like this
{...
'state': {'state': 'Pending'}}
}
Interesting question, and well put.
Using SlugRelatedField('state__label', queryset=...) works fine, with 1 caveat: its just calling queryset.get(state__label="x") which errors if there isn't exactly 1 match.
1) Write a custom field?
Inherit from SlugRelatedField and override to_internal_value(), maybe by calling .first() instead of .get(), or whatever other logic you need.
2) Re-evaluate this relationship, maybe its 1-to-1? a choice field?
I'm a bit confused on how this would all work, since you can have a "1 to many" with State => ReviewState. The default lookup (if you don't do #1) will throw an error when multiple matches occur.
Maybe this is a 1-to-1 situation with the model? Perhaps the ReviewState can use a ChoiceField instead of a table of states?
Perhaps the 'label' can be the PK of the State table, and also a SlugField rather than a non-unique CharField?
3) Write different serializers for the List and Create cases
DRF doesn't give us a built-in way to do this, but this reliance on "one serializer to do it all" is the cause of a lot of problems I see on SO. Its just really hard to get what you want without having different serializers for different cases. It's not hard to roll-your-own mixin to do it, but here's an example which uses an override:
from rest_framework import serializers as s
class MyCreateSerializer(s.ModelSerializer):
state = s.SlugRelatedField(...)
...
class MyListSerializer(s.ModelSerializer):
# use dotted notation, serializers read *object* attributes
state = s.CharField(source="state.state.label")
...
class MyViewSet(ModelViewSet):
queryset = MyModel.objects.select_related('state__state')
...
def get_serializer_class(self):
if self.action == "create":
return MyCreateSerializer
else:
return MyListSerializer

Conditionals in django models arguments

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"

Django model fields getter / setter

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/

Django, query filtering from model method

I have these models:
def Foo(Models.model):
size = models.IntegerField()
# other fields
def is_active(self):
if check_condition:
return True
else:
return False
def Bar(Models.model):
foo = models.ForeignKey("Foo")
# other fields
Now I want to query Bars that are having active Foo's as such:
Bar.objects.filter(foo.is_active())
I am getting error such as
SyntaxError at /
('non-keyword arg after keyword arg'
How can I achieve this?
You cannot query against model methods or properties. Either use the criteria within it in the query, or filter in Python using a list comprehension or genex.
You could also use a custom manager. Then you could run something like this:
Bar.objects.foo_active()
And all you have to do is:
class BarManager(models.Manager):
def foo_active(self):
# use your method to filter results
return you_custom_queryset
Check out the docs.
I had similar problem: I am using class-based view object_list and I had to filter by model's method. (storing the information in database wasn't an option because the property was based on time and I would have to create a cronjob and/or... no way)
My answer is ineffective and I don't know how it's gonna scale on larger data; but, it works:
q = Model.objects.filter(...)...
# here is the trick
q_ids = [o.id for o in q if o.method()]
q = q.filter(id__in=q_ids)
You can't filter on methods, however if the is_active method on Foo checks an attribute on Foo, you can use the double-underscore syntax like Bar.objects.filter(foo__is_active_attribute=True)

Django Managers - Retrieving objects with non-empty set of related objects

I have two classes, Portfolio, and PortfolioImage.
class PortfolioImage(models.Model):
portfolio = models.ForeignKey('Portfolio', related_name='images')
...
class Portfolio(models.Model):
def num_images(self):
return self.images.count()
I want to write a "non-empty portfolio" manager for Portfolio, so that I can do:
queryset = Portfolio.nonempty.all()
I've tried doing something like this, but I don't think this is even close:
class NonEmptyManager(models.Manager):
def get_query_set(self):
return super(NonEmptyManager, self).get_query_set().filter(num_images > 0)
I don't really know where to start, and I'm finding the documentation a bit lacking in this area.
Any ideas? Thanks,
First of all according to documentation you cannot use model methods for lookup with filter/exclude clause. Then also you cannot use python operators (> in your case) with filter/exclude.
To resolve your task if you are using Django 1.1beta:
from django.db.models import Count
#...
def get_query_set(self):
return super(NonEmptyManager,self).get_query_set()\
.annotate(num_images=Count('images'))\
.filter(num_images__gt=0)
But this solution has some limitations.
Another way for Django >= 1.0:
def get_query_set(self):
return super(NonEmptyManager,self).get_query_set()\
.filter(images__isnull=True)