Django Custom Manager add attribute - django

I have a Django Model that looks like this:
class MyModel(models.Model):
field1 = models.IntegerField()
field2 = models.IntegerField()
nonDbField = SomeObject()
objects = MyCustomManager()
field1 is actually a PK to an abstract class of SomeObject.
I want a custom manager that for every value returned by any of the functions (all, filter, get, etc) does the following:
value.nonDbField = SomeObject.objects.get(pk=value.field1)
I've tested that I can manually override get like so:
class MyCustomManager(models.Manager):
def get(self, *args, **kwargs):
value = super(MyCustomManager, self).get(*args, **kwargs)
value.nonDbField = SomeObject.objects.get(listid=value.itemListID)
return value
but wondered if there was an easier way to do it across all functions.
There's going to be plenty of you that will say, "Why are you doing this?". It has to do with a model inheritance of a legacy, but still active database.

If you need nonDbField's value to be related to the field1 (or any other field in the model) you can try something like this:
Class MyModel(models.Model):
# your fields here...
def _nonDbField(self):
return SomeObject.objects.get(pk=self.field1)
nonDbField = property(_nonDbField)
This allows you to do something like this:
MyModel.objects.get(pk=1).nonDbField
Keep in mind that you are making a database query each time you access nonDbField (which may or may not be detrimental to your DB performance).

you can use property for your calculated fields
Class MyModel(models.Model):
# your fields here...
first_name = models.CharField()
last_name = models.CharField()
#property
def fullname(self):
return f"{self.first_name} {self.last_name}"
This allows you to do something like this:
obj = MyModel.objects.get(pk=1)
print(obj.fullname)

Related

unique id generator in a custom format in django

i want to generate an unique id code in my model
for example -
id want to generate
Can anyone Kindly help me in the same?
i Want id in the table as shown below-
class parameter(models.Model)
name=models.models.CharField(max_length=50)
type=model.#should come from another table
subtype=model.#should come from another table
id= # should compile all the above as in picture
thanks
I think you should not store the unique id in DB, because you can easily generate it by a property method:
class Parameter(models.Model)
name=models.models.CharField(max_length=50)
type=model.ForeignKey(Type)
subtype=model.ForeignKey(SubType)
product_type=model.ForeignKey(ProductType)
serial_no = models.CharField()
#property
def generated_id(self):
return '{}/{}/{}/{}'.format(self.type_id, self.subtype_id, self.product_type_id, self.serial_no.zfill(2))
If you intend to display it in admin site, then simply try like this:
#admin.register(Parameter)
class ParameterAdmin(admin.ModelAdmin):
model = Parameter
fields = ['type', 'sub_type', 'product_type', 'serial_no', 'generated_id']
readonly_fields = ('generated_id',)
Update
If you want to store in in DB, then you need to override the save method of the models. Like this:
class Parameter(models.Model)
name=models.models.CharField(max_length=50)
type=model.ForeignKey(Type)
subtype=model.ForeignKey(SubType)
product_type=model.ForeignKey(ProductType)
serial_no = models.CharField()
generated_id = models.CharField()
def generate_id(self):
return '{}/{}/{}/{}'.format(self.type_id, self.subtype_id, self.product_type_id, self.serial_no.zfill(2))
def save(self, *args, **kwargs):
self.generated_id = self.generate_id()
super().save(*args, **kwargs)

Accessing parent model instance within model admin to provide custom queryset

I want to provide a custom queryset within a model admin class that inherits from TabluarInline, but I want to provide this queryset by calling a method of current instance of the model object.
I have two models. One for tracks belonging to an album, and one for the Album itself. Some tracks can be hidden and I have a method in Album to return only the visible tracks.
class Track(models.Model):
name = models.CharField()
length = models.IntegerField()
album = ForeignKey(Album)
hidden = BooleanField()
class Album(models.Model):
name = models.CharField()
def get_visible_tracks_queryset(self):
return self.track_set.filter(hidden=False)
And I have a tracks inline admin which is included on the django admin page for an album. I want to re-use the get_visible_tracks_queryset to define the queryset for this inline admin, I don't want to repeat the logic again. I can't figure out how to do it. I could do something like the following, however I'm using a simplified example here, I actually have more complex logic and I don't want to be repeating the logic in multiple places.
class TracksInlineAdmin(admin.TabularInline):
fields = ("name", "length")
model = Track
def get_queryset(self, request):
qs = super(TracksInlineAdmin, self).get_queryset(request)
return qs.filter(hidden=False)
Ideally I could do something like:
class TracksInlineAdmin(admin.TabularInline):
fields = ("name", "length")
model = Track
def get_queryset(self, request, parent_model_instance):
return parent_model_instance.get_visible_tracks_queryset()
Any thoughts on how to achieve this?
The cleanest way is to define a custom QuerySet class for your model in which you can define any complex filters for re-use in various places:
class Track(models.Model):
# fields defined here
objects = TrackManager()
class TrackManager(models.Manager):
def get_queryset(self):
return TrackQuerySet(self.model, using=self._db)
class TrackQuerySet(models.QuerySet):
def visible(self):
return self.filter(hidden=False)
Now, anywhere in code, when you have a queryset of tracks (e.g. Track.objects.filter(name="my movie")) you can add .visible() to filter further. Also on a related set:
album.track_set.all().visible()

Django: Use select_related without a ForeignKey field

I have two models which are used with a database I don't control. Both are set with managed = False. The first model has a field which is a foreign key to the second model, but it's implemented as a CharField, not as a ForeignKey.
Is it possible to use select_related on the first model to access properties of the key'd second model?
Here's an example:
class Foo(models.Model):
class Meta:
managed = False
fieldone = models.CharField(max_length=10)
myfk = models.CharField(max_length=20) # In practice, this points to Bar.localkey
class Bar(models.Model):
class Meta:
managed = False
localkey = models.CharField(max_length=20)
someotherattribute = models.CharField(max_length=100)
Foo.objects.all().select_related('Bar') # I know this won't work, but is there something that will?
No, because there's nothing related.
But if you (or someone for some reason) have stored the ID (or some unique value such as localkey) from the 'related' object, you could perform a filter based on it.
foo = Foo.objects.first() # Pick one Foo object
foo_bar = Bar.objects.get(localkey=foo.myfk)
To make this looks like select_related you could try this:
class Foo(models.Model):
class Meta:
managed = False
fieldone = models.CharField(max_length=10)
myfk = models.CharField(max_length=20)
def bar(self):
return Bar.objects.get(localkey=self.myfk)
# probably you will need to manage common error when performing a .get()
# DoesNotExist and MultipleObjectsReturned
Then use like this:
foos = Foo.objects.all()
for foo in foos:
print foo.bar()
I am not sure if this is a good idea but you could decorate .bar() method as a property:
...
#property
def bar(self):
return Bar.objects.get(localkey=self.myfk)
And then call it like this:
foo # some random Foo object
foo.bar # this should return the 'related' Bar object

Django Model Manager related filter

I'd like to filter the related entries in the manager:
class UserTravelsCarsManager(models.Manager):
def for_user(self, user):
return super(UserTravelsCarsManager, self).get_query_set().filter(user=user)
class TravelsCars(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=255)
...
objects = UserTravelsCarsManager()
class UserTravelsManager(models.Manager):
def for_user(self, user):
return super(UserTravelsManager, self).get_query_set().filter(user=user)
class Travels(models.Model, ClonableMixin):
user = models.ForeignKey(User)
vehicle = models.ForeignKey(TravelsCars)
...
objects = UserTravelsManager()
It won't work by itself. I get all of the cars for all users. I've tried:
return super(UserTravelsManager, self).get_query_set().filter(user=user, vehicle__user=user)
Which also doesn't work.
UPDATE:
Just to be clear the entries for Travels are filtered. Just the related TravelsCars aren't filtered if I query them through Travels.
What am I doing wrong?
Instead of super(UserTravelsCarsManager, self).get_query_set().filter... try to use self.filter(user=user). Same in UserTravelsManager

Django: Adding property to User model after creating model based on abstract class

I have a normal model and an abstract model like so:
class TaggedSubject(models.Model):
user = models.ForeignKey(User, null=True, blank=True)
category = models.CharField(max_length=200)
foo = models.CharField(max_length=50)
bar = models.CharField(max_length=50)
# etc
content_type = models.ForeignKey(ContentType)
content_object_pk = models.CharField(max_length=255)
content_object = generic.GenericForeignKey("content_type", "content_object_pk")
def __unicode__(self):
if self.user:
return "%s" % (self.user.get_full_name() or self.user.username)
else:
return self.label
class Taggable(models.Model):
tagged_subjects = generic.GenericRelation(TaggedSubject, content_type_field='content_type', object_id_field='content_object_pk')
#property
def tagged_users(self):
return User.objects.filter(pk__in=self.tagged_subjects.filter(user__isnull=False).values("user"))
class Meta:
abstract = True
The Taggable abstract model class then gets used like so:
class Photo(Taggable):
image = models.ImageField(upload_to="foo")
# ... etc
So if we have a photo object:
photo = Photo.objects.all()[0]
I can all the users tagged in the photo with photo.tagged_users.all()
I want to add the inverse relation to the user object, so that if I have a user:
user = User.objects.filter(pk__in=TaggedSubject.objects.exclude(user__isnull=True).values("user"))[0]
I can call something like user.tagged_photo_set.all() and have it return all the photo objects.
I suspect that since TaggedSubject connects to the Taggable model on a generic relation that it won't be possible to use it as a through model with a ManyToMany field.
Assuming this is true, this is the function I believe I'd need to add (somehow) to the User model:
def tagged_photo_set(self):
Photo.objects.filter(pk__in=TaggedSubject.objects.filter(user=self, content_type=ContentType.objects.get_for_model(Photo))
I'm wondering if it's possible to set it up so that each time a new model class is created based on Taggable, it creates a version of the function above and adds it (ideally as a function that behaves like a property!) to User.
Alternatively, if it is somehow possible to do ManyToMany field connections on a generic relation (which I highly doubt), that would work too.
Finally, if there is a third even cooler option that I am not seeing, I'm certainly open to it.
You could use add_to_class and the class_prepared signal to do some post processing when models subclassing your base class are set up:
def add_to_user(sender, **kwargs):
def tagged_FOO_set(self):
return sender.objects.filter(pk__in=TaggedSubject.objects.filter(
user=self,
content_type=ContentType.objects.get_for_model(sender)))
if issubclass(sender, MyAbstractClass):
method_name = 'tagged_{model}_set'.format(model=sender.__name__.lower())
User.add_to_class(method_name, property(tagged_FOO_set))
class_prepared.connect(add_to_user)