Can a model manager access its models' Meta attribute (`Meta.unique_together`)? - django

Here's my attempt at a generalized natural key model manager. It's like the docs except it tries (unsuccessfully) to determine the natural key field names from the Meta.unique_together attribute.
class NaturalKeyModelManager(Manager):
def get_by_natural_key(self, *args):
field_dict = {}
for i, k in enumerate(self.model.Meta.unique_together[0]):
field_dict[k] = args[i]
return self.get(**field_dict)
If I insert a debug print just before the for loop like this:
print dir(self.model.Meta)
it doesn't list the unqiue_together attribute at all:
['__doc__', '__module__', 'abstract']
The 'abstract' bit worried me, but another debug print shows that the model I'm trying manage with natural keys is not abstract:
>>> print self.model.Meta.abstract
False
I am mixing in a lot of abstract base classes. Could that be the problem?
class MixedModel(NamedModel, TimeStampedModel, VersionedModel, Model):
objects = NaturalKeyModelManager()
class Meta:
unique_together = (('name', 'version',),)
For completeness here's one of the mixins:
class TimeStampedModel(Model):
created = DateTimeField(_("Created"), auto_now_add=True, null=True, editable=False)
updated = DateTimeField(_("Updated"), auto_now=True, null=True, editable=True)
class Meta:
abstract = True
The hard-coded model manager works just fine:
class MixedModelManager(Manager):
def get_by_natural_key(self, name, version):
return self.get(name=name, version=version)

In order to get the actual options passed to meta, you should use self.model._meta rather than self.model.Meta

Related

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 Tastypie Reference the Same ForeignKey Model More Than Once

Is there a way to reference the same ForeignKey model/resource more than once in Tastypie?
Assume the models:
class Case(models.Model):
name = models.CharField(max_length=10)
class Interaction(models.Model):
case = models.ForeignKey(Case, related_name="interaction_cases")
type = models.CharField(max_length=2, choices=TYPE_CHOICES)
Assume the TastyPie resources:
class CaseResource(ModelResource):
type_one_interactions = fields.ManyToManyField('TypeOneInteractionFullResource', 'interaction_cases', null=True, full_list=True, full=True)
type_two_interactions = fields.ManyToManyField('TypeTwoInteractionFullResource', 'interaction_cases', null=True, full_list=True, full=True)
class Meta:
queryset = Case.objects.all()
class TypeOneInteractionResource(ModelResource):
case = fields.ForeignKey(Case,'case')
class Meta:
queryset = Interaction.objects.all()
def get_object_list(self, request):
return super(TypeOneInteractionResource, self).get_object_list(request).filter(type='A')
class TypeTwoInteractionResource(ModelResource):
case = fields.ForeignKey(Case,'case')
class Meta:
queryset = Interaction.objects.all()
def get_object_list(self, request):
return super(TypeTwoInteractionResource, self).get_object_list(request).filter(type='B')
Basically I am trying to create a single resource with two reverse resources to the same model with different data. When I access the CaseResource I see both TypeOneInteractionResource and TypeTwoInteractionResource in the result, but the data is not being filtered correctly.
I assume it has something to do with the "related_name" being the same and the way TastyPie does model joining internally. Has anybody been successful doing this? Is it even possible?
The reason is because get_object_list is not called at all when dehydrating the ToManyField for related resources (see https://github.com/toastdriven/django-tastypie/blob/master/tastypie/fields.py#L780).
Instead, you'd want to use the dehydrate_type_one_interactions and dehydrate_type_two_interactions methods on the CaseResource.
On the other hand, you can provide properties on the Case model that would return desired QuerySets and use those properties for attribute names in ManyToManyFields.

Unable to use natural keys in django foreign key serialization

So the thing is I have a class which has a foreign key.
This is my code
class Proxy(models.Model):
class Meta:
db_table = 'Proxy'
equipment = models.ForeignKey('Equipment', primary_key=True)
pop = models.ForeignKey('Pop')
Now, as usual when I do
import django.core.serializers as Serializer
res = Proxy.objects.filter(equipment_id__exact='eq1')
Serializer.serialize('json', res)
the json output contains the "id" of the Pop and not the name, which I want.
So I used the Manager class and this is my Pop class now-
class PopManager(models.Manager):
def get_by_natural_key(self, name):
return self.get(name=name)
class Pop(models.Model):
POP_TYPES = (
('phy','phy'),
('cloud','cloud'),
)
class Meta:
db_table = 'Pop'
unique_together = ('name', 'vip')
objects = PopManager()
name = models.CharField(max_length=10)
type = models.CharField(max_length=10, choices=POP_TYPES)
def natural_key(self):
return (self.name)
But after this, when I do
res = Proxy.objects.filter(equipment_id__exact='eq1')
Serializer.serialize('json', res, use_natural_keys=True)
I get an error,
TypeError: Equipment: eq1 is not JSON serializable
I have also tried wadofstuff for this serialization of foreign keys, but apparently in Django1.5, there is a clash between simplejson and json, and the query was throwing an error. So I am back to square one.
Any help will be highly appereciated. I have been splitting my hair for hours.
Anyway, so I solved this by converting the QuerySet to a raw dictionary type format. I appended a values clause after filter. And looped through the entire queryset returned to make a list of dictionaries. The rest was easy.
Agniva,
You just need to delete your line:
db_table = 'Pop'

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)

Using computed fields in admin

I'm trying to use a runtime-computed field in my admin page. This works fine, but I'd like to allow sorting based for that field. Using Django 1.5 (dev), is this possible? I've been scouring the interweb but can't find anything indicating that it is possible.
class Guest(models.Model)
email = models.CharField(max_length=255)
class Invitation(models.Model)
guest = models.ForeignKey(Guest)
created_on = models.DateTimeField(auto_now_add=True)
class GuestAdmin(admin.ModelAdmin):
list_display = ["email", "latest_invitation_sent_on",]
def latest_invitation_sent_on(self, o):
try:
return o.invitation_set.all().order_by(
"-created_on")[0].created_on.strftime("%B %d, %Y")
except IndexError:
return "N/A"
I'd like to be able to enable sorting by latest_invitation_sent_on. Are there any methods of doing this nicely that I'm unaware of?
You should be able to annotate Guests with their latest invitation time and then order_by it (order_by uses the DB to sort and as long as you can provide a valid DB field, table or virtual it should work).
class GuestManager(models.Manager):
def get_query_set(self):
return super(GuestManager, self).get_query_set().annotate(latest_invite=Max("invitation_set__created_on"))
class Guest(models.Model)
email = models.CharField(max_length=255)
objects = GuestManager()
class Invitation(models.Model)
guest = models.ForeignKey(Guest)
created_on = models.DateTimeField(auto_now_add=True)
class GuestAdmin(admin.ModelAdmin):
list_display = ["email", "latest_invite",]
If you only need latest_invite annotation once in a while it makes sense to move it to a separate method or even manager.
class GuestManager(models.Manager):
def by_invitations(self):
return super(GuestManager, self).get_query_set().annotate(latest_invite=Max("invitation_set__created_on")).order_by('-latest_invite')
>>> Guest.objects.by_invitations()