GAE NDB Expando Model with dynamic Kind - python

Is it possible to assign a dynamic Entity Kind to an Expando Model? For example, I want to use this model for many types of dynamic entities:
class Dynamic(ndb.Expando):
"""
Handles all "Post types", such as Pages, Posts, Users, Products, etc...
"""
col = ndb.StringProperty()
parent = ndb.IntegerProperty()
name = ndb.StringProperty()
slug = ndb.StringProperty()
Right now I use the "col" StringProperty to hold the Kind (like "Pages", "Posts", etc) and query for the "col" every time.
After reading the docs, I stumbled upon this #classmethod:
class MyModel(ndb.Model):
#classmethod
def _get_kind(cls):
return 'AnotherKind'
Does that mean I can do this?
class Dynamic(ndb.Expando):
"""
Handles all "Post types", such as Pages, Posts, Users, Products, etc...
"""
col = ndb.StringProperty()
parent = ndb.IntegerProperty()
name = ndb.StringProperty()
slug = ndb.StringProperty()
#classmethod
def _get_kind(cls):
return 'AnotherKind'
But how do I dynamically replace 'AnotherKind'? Can I do something like return col?
Thanks!

I don't know if you can do that, but it sounds dangerous, and GAE updates might break your code.
Using subclasses seems like a much safer alternative. Something like this:
class Dynamic(ndb.Expando):
parent = ndb.IntegerProperty()
name = ndb.StringProperty()
slug = ndb.StringProperty()
class Pages(Dynamic):
pass
class Posts(Dynamic):
pass
class Users(Dynamic):
pass
You could also try using PolyModel.
We need to know more about your application and what you are trying to accomplish to give more specific advice.

Related

Django model inheritance with proxy classes

I've got proxy classes which have been created mainly to implement custom filtering, but there are some other fairly small custom methods as well, and they will be expanded to provide other custom logic as well.
So say I have models:
class Videos(models.Model):
title = models.CharField(max_length=200)
publisher = models.Charfield(max_length=100)
release_date = models.DateField()
class Superheroes(Videos):
objects = SuperheroesManager()
class Meta:
proxy = True
class Recent(Videos):
objects = RecentManager()
class Meta:
proxy = True
and model managers:
class SuperheroesManager():
def get_queryset(self):
return super().get_queryset().filter(publisher__in=['Marvel','DC'])
class RecentManager():
def get_queryset(self):
return super().get_queryset().filter(release_date__gte='2020-01-01')
On the front end a user may pick a category which corresponds to one of the proxy classes. What would be the best way to maintain a mapping between the category which is passed to the view and the associated proxy class?
Currently I have an implicit dependency whereby the category name supplied by the front end must be the same as the proxy class name, allowing for a standard interface in the view:
def index(request, report_picked)
category = getattr(sys.modules[__name__], report_picked)
videos = category.objects.all()
I'd like to move away from this implicit dependency, but not sure what the best way would be.
I wouldn't want to maintain a dictionary and can't use a factory method either as that should return a fully initialised object whereas I just need the class returned.
What would be the best way to implement this?
I've decided to set the category name used by the front end as a class variable:
class Superheroes(Videos):
category = 'superheroes'
objects = SuperheroesManager()
class Meta:
proxy = True
And so the view just loops through all the models, and returns the model whose category matches the provided value from the front end:
from django.apps import apps
def index(request, report_picked):
for model in apps.get_models():
try:
print(f"Report picked: {report_picked}, model: {model.name}")
if model.category == report_picked.lower():
category = model
break
except AttributeError:
pass
I'd be curious to know if there is any better alternatives though.

How to keep DRY while creating common models in Django?

For example I have 2 main models in my django app:
class Employee(Model):
name = CharField(max_length=50)
class Client(Model):
title = CharField(max_length=50)
Abstract base class for phones:
class Phone(Model):
number = CharField(max_length=10)
class Meta:
abstract = True
Inherited separate classes for Employee and for Client:
class EmployeePhone(Phone):
employee = ForeignKey(Employee, on_delete=CASCADE, related_name='employee_phones')
class ClientPhone(Phone):
client = ForeignKey(Client, on_delete=CASCADE, related_name='client_phones')
It works but I don't like it, I would prefer to keep just one Phone model instead of 3. I know I could use Generic-Models but unfortunately that's not longer an option, because my app is actually REST-API and it seems to be impossible to create Generic-Object while creating Parent-Object. So is there any solution to keep things clean and DRY ?
Other answers present good ideas how you can normalise the database, but if you'd like to keep the schema the same and just avoid repeating the same thing in the code, maybe a custom field subclass is what you're after?
Example:
# fields.py
class PhoneField(models.CharField):
def __init__(self, **kwargs):
kwargs.setdefault('max_length', 50)
...
super().__init__(**kwargs)
# models.py
class Employee(models.Model):
phone = PhoneField()
How about keeping 1 Phone class and linking Employee and Customer to Phone via a Foreign Key and removing the abstract :).
How about moving EmployeePhone (ClientPhone) as a ManyToManyField in Employee (Client) model related to Phone model?
class Employee(Model):
name = CharField(max_length=50)
phones = ManyToManyField(Phone, ...)

Google App Engine - Multiple child/parent

I want to make friend system that have db model like this:
class Users(ndb.Model):
username = ndb.StringProperty(required = True)
bio = ndb.StringProperty()
class friend_list(ndb.Model):
list = ndb.StringProperty(repeated=True)
class friend_pending(ndb.Model):
list = ndb.StringProperty(repeated=True)
friend_pending is model for friend that not yet accepted. While friend_list is model for friend that are accepted.
I want to make both friend_list and friend_pending to be child of Users entity. Is it possible?
Here's the second approach if it is not possible:
class Users(ndb.Model):
username = ndb.StringProperty(required = True)
bio = ndb.StringProperty()
class friend_list(ndb.Model):
user_username = ndb.StringProperty(required = True)
list = ndb.StringProperty(repeated=True)
class friend_pending(ndb.Model):
user_username = ndb.StringProperty(required = True)
list = ndb.StringProperty(repeated=True)
If both are possible, which are better for cost and performance?
I want to make both friend_list and friend_pending to be child of Users entity. Is it possible?
Yes. When you create an entity, you can use the "parent" parameter to designate a parent (or parents) for the entity.
Google's Entity Keys section covers this well.
Example:
#Create User entity
#This code assumes you're using GAE's built-in user's class
user = users.User("Albert.Johnson#example.com")
user.put()
#Create a friend list and set its parent to the user we create above
friend_list = Friend_List(parent=user)
#Save to datastore
friend_list.put()​
Keep in mind that the Users class in GAE is specially defined and has additional functions that you need to acknowledge. See the documentation here.
If both are possible, which are better for cost and performance?
I can't say for sure because I don't know exactly how you will be using these models, but in most(maybe all) cases your first approach would be more efficient.
Lastly, the correct naming convention for Datastore models is to capitalize the first letter. For example, your friend list class should be "Friend_List".

Django QuerySet with Models

I'm new to Django and trying to understand how to use querysets with models.
Model
class Channel(models.Model):
name = models.CharField(max_length=200)
accountid = models.CharField(max_length=34)
def get_channel_list(self):
return self.get_queryset().name()
What I want to do is return the entire name column as an array if account id matches. I'd like to use a function in the models.py but I haven't found an online sample that caters to what I'm looking for.
The above isn't returning any data even without a filter.
Any point in the right direction would be amazing.
Use objects.filter and classmethod:
class Channel(models.Model):
name = models.CharField(max_length=200)
accountid = models.CharField(max_length=34)
#classmethod
def get_channel_list(cls, acc):
return cls.objects.filter(accountid=acc).values_list('name', flat=True)
There is another technique to do such things in django - define custom manager to model. (for example, you have several Channel models inherited from one base proxy model and you want to put same get_channel_list functions to some models - custom Manager is the way to go):
class ChannelManager(models.Manager):
def get_channel_list(self, acc):
return self.filter(accountid=acc).values_list('name', flat=True)
class Channel(models.Model):
name = models.CharField(max_length=200)
accountid = models.CharField(max_length=34)
objects = ChannelManager()
You have failed to understand the difference between managers and models. It's the manager that it's responsible for creating queries, and which has the get_queryset method. From a model, you need to access the manager, which is usually named objects. Note, you cannot do that from an instance, so this needs to be a classmethod.
#classmethod
def get_channel_list(cls, accountid):
return cls.objects.filter(accountid=accountid).values_list('name')

In Django - Model Inheritance - Does it allow you to override a parent model's attribute?

I'm looking to do this:
class Place(models.Model):
name = models.CharField(max_length=20)
rating = models.DecimalField()
class LongNamedRestaurant(Place): # Subclassing `Place`.
name = models.CharField(max_length=255) # Notice, I'm overriding `Place.name` to give it a longer length.
food_type = models.CharField(max_length=25)
This is the version I would like to use (although I'm open to any suggestion):
http://docs.djangoproject.com/en/dev/topics/db/models/#id7
Is this supported in Django? If not, is there a way to achieve similar results?
Updated answer: as people noted in comments, the original answer wasn't properly answering the question. Indeed, only the LongNamedRestaurant model was created in database, Place was not.
A solution is to create an abstract model representing a "Place", eg. AbstractPlace, and inherit from it:
class AbstractPlace(models.Model):
name = models.CharField(max_length=20)
rating = models.DecimalField()
class Meta:
abstract = True
class Place(AbstractPlace):
pass
class LongNamedRestaurant(AbstractPlace):
name = models.CharField(max_length=255)
food_type = models.CharField(max_length=25)
Please also read #Mark answer, he gives a great explanation why you can't change attributes inherited from a non-abstract class.
(Note this is only possible since Django 1.10: before Django 1.10, modifying an attribute inherited from an abstract class wasn't possible.)
Original answer
Since Django 1.10 it's
possible!
You just have to do what you asked for:
class Place(models.Model):
name = models.CharField(max_length=20)
rating = models.DecimalField()
class Meta:
abstract = True
class LongNamedRestaurant(Place): # Subclassing `Place`.
name = models.CharField(max_length=255) # Notice, I'm overriding `Place.name` to give it a longer length.
food_type = models.CharField(max_length=25)
No, it is not:
Field name “hiding” is not permitted
In normal Python class inheritance, it is permissible for a child
class to override any attribute from the parent class. In Django, this
is not permitted for attributes that are Field instances (at least,
not at the moment). If a base class has a field called author, you
cannot create another model field called author in any class that
inherits from that base class.
That is not possible unless abstract, and here is why: LongNamedRestaurant is also a Place, not only as a class but also in the database. The place-table contains an entry for every pure Place and for every LongNamedRestaurant. LongNamedRestaurant just creates an extra table with the food_type and a reference to the place table.
If you do Place.objects.all(), you also get every place that is a LongNamedRestaurant, and it will be an instance of Place (without the food_type). So Place.name and LongNamedRestaurant.name share the same database column, and must therefore be of the same type.
I think this makes sense for normal models: every restaurant is a place, and should have at least everything that place has. Maybe this consistency is also why it was not possible for abstract models before 1.10, although it would not give database problems there. As #lampslave remarks, it was made possible in 1.10. I would personally recommend care: if Sub.x overrides Super.x, make sure Sub.x is a subclass of Super.x, otherwise Sub cannot be used in place of Super.
Workarounds: You can create a custom user model (AUTH_USER_MODEL) which involves quite a bit of code duplication if you only need to change the email field. Alternatively you can leave email as it is and make sure it's required in all forms. This doesn't guarantee database integrity if other applications use it, and doesn't work the other way around (if you want to make username not required).
See https://stackoverflow.com/a/6379556/15690:
class BaseMessage(models.Model):
is_public = models.BooleanField(default=False)
# some more fields...
class Meta:
abstract = True
class Message(BaseMessage):
# some fields...
Message._meta.get_field('is_public').default = True
My solution is as simple as next monkey patching, notice how I changed max_length attribute of name field in LongNamedRestaurant model:
class Place(models.Model):
name = models.CharField(max_length=20)
class LongNamedRestaurant(Place):
food_type = models.CharField(max_length=25)
Place._meta.get_field('name').max_length = 255
Pasted your code into a fresh app, added app to INSTALLED_APPS and ran syncdb:
django.core.exceptions.FieldError: Local field 'name' in class 'LongNamedRestaurant' clashes with field of similar name from base class 'Place'
Looks like Django does not support that.
This supercool piece of code allows you to 'override' fields in abstract parent classes.
def AbstractClassWithoutFieldsNamed(cls, *excl):
"""
Removes unwanted fields from abstract base classes.
Usage::
>>> from oscar.apps.address.abstract_models import AbstractBillingAddress
>>> from koe.meta import AbstractClassWithoutFieldsNamed as without
>>> class BillingAddress(without(AbstractBillingAddress, 'phone_number')):
... pass
"""
if cls._meta.abstract:
remove_fields = [f for f in cls._meta.local_fields if f.name in excl]
for f in remove_fields:
cls._meta.local_fields.remove(f)
return cls
else:
raise Exception("Not an abstract model")
When the fields have been removed from the abstract parent class you are free to redefine them as you need.
This is not my own work. Original code from here: https://gist.github.com/specialunderwear/9d917ddacf3547b646ba
Maybe you could deal with contribute_to_class :
class LongNamedRestaurant(Place):
food_type = models.CharField(max_length=25)
def __init__(self, *args, **kwargs):
super(LongNamedRestaurant, self).__init__(*args, **kwargs)
name = models.CharField(max_length=255)
name.contribute_to_class(self, 'name')
Syncdb works fine. I dont tried this example, in my case I just override a constraint parameter so ... wait & see !
I know it's an old question, but i had a similar problem and found a workaround:
I had the following classes:
class CommonInfo(models.Model):
image = models.ImageField(blank=True, null=True, default="")
class Meta:
abstract = True
class Year(CommonInfo):
year = models.IntegerField()
But I wanted Year's inherited image-field to be required while keeping the image field of the superclass nullable. In the end I used ModelForms to enforce the image at the validation stage:
class YearForm(ModelForm):
class Meta:
model = Year
def clean(self):
if not self.cleaned_data['image'] or len(self.cleaned_data['image'])==0:
raise ValidationError("Please provide an image.")
return self.cleaned_data
admin.py:
class YearAdmin(admin.ModelAdmin):
form = YearForm
It seems this is only applicable for some situations (certainly where you need to enforce stricter rules on the subclass field).
Alternatively you can use the clean_<fieldname>() method instead of clean(), e.g. if a field town would be required to be filled in:
def clean_town(self):
town = self.cleaned_data["town"]
if not town or len(town) == 0:
raise forms.ValidationError("Please enter a town")
return town
You can not override Model fields, but its easily achieved by overriding/specifying clean() method. I had the issue with email field and wanted to make it unique on Model level and did it like this:
def clean(self):
"""
Make sure that email field is unique
"""
if MyUser.objects.filter(email=self.email):
raise ValidationError({'email': _('This email is already in use')})
The error message is then captured by Form field with name "email"

Categories