Determine if an M2M field has a custom through model - python

With a custom through model for an M2M relationship, .add(), .create() and .remove() are disabled.
At the moment, I attempt to use .add() (or whatever) and catch and deal with AttributeError for those custom M2M relationships.
Is there an 'official' way to identify a custom through model, using the Meta API or otherwise? At this stage in my processing, I would rather treat all custom through relationships as generically as possible (rather than lots of if m2m_field.related.through == FooBar statements)
(A solution was found for Django 1.8, but I'm starting a bounty for 2.2)

Looks as though
m2m_field.related.through._meta.auto_created is False
does the job.

From Django 2.2 the .add(), .create(), etc. methods are able to work with a custom through Model as long as you provide the corresponding values for the required fields of the intermediate model using through_defaults:
From the documentation:
You can also use add(), create(), or set() to create relationships, as long as you specify through_defaults for any required fields:
>>> beatles.members.add(john, through_defaults={'date_joined': date(1960, 8, 1)})
>>> beatles.members.create(name="George Harrison", through_defaults={'date_joined': date(1960, 8, 1)})
>>> beatles.members.set([john, paul, ringo, george], through_defaults={'date_joined': date(1960, 8, 1)})
You may prefer to create instances of the intermediate model directly.
The .remove() method's behavior needs a bit of attention:
If the custom through table defined by the intermediate model does not enforce uniqueness on the (model1, model2) pair, allowing multiple values, the remove() call will remove all intermediate model instances:
>>> Membership.objects.create(person=ringo, group=beatles,
... date_joined=date(1968, 9, 4),
... invite_reason="You've been gone for a month and we miss you.")
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>, <Person: Ringo Starr>]>
>>> # This deletes both of the intermediate model instances for Ringo Starr
>>> beatles.members.remove(ringo)
>>> beatles.members.all()
<QuerySet [<Person: Paul McCartney>]>
About the _meta field I haven't been able to access it from the m2m field, but the above part of the documentation seems to allow avoiding the "gymnastics" of accessing _meta.
If I find anything interesting I will update my answer accordingly.

For django 2.2 you can directly check the whether the through model is autocreated or not
This can be checked through a direct check like the following
# check for the auto_created value
m2m_field.through._meta.auto_created == False
To test this I created some sample clases with multiple M2M fields one with a custom through field and one with default class.
from django.db import models
import uuid
class Leaf(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(null=True, max_length=25, blank=True)
# Create your models here.
class MainTree(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
new_leaves = models.ManyToManyField(Leaf, through='LeafTree')
leaves = models.ManyToManyField(Leaf, related_name='main_branch')
class LeafTree(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
tree = models.ForeignKey(MainTree, on_delete=models.CASCADE)
leaf = models.ForeignKey(Leaf, on_delete=models.CASCADE)
Testing our method
In [2]: from trees.models import MainTree
In [3]: m = MainTree()
In [4]: m.leaves.through._meta
Out[4]: <Options for MainTree_leaves>
In [5]: m.leaves.through._meta.auto_created
Out[5]: trees.models.MainTree
In [6]: m.new_leaves.through._meta.auto_created
Out[6]: False
In [7]: m.new_leaves.through._meta.auto_created == False
Out[7]: True
In [8]: m.leaves.through._meta.auto_created == False
Out[8]: False

Related

How can I make the get_random_string Django module to return only numerical values?

What should I do to my simple code below, so that it only returns numerical values?
>>> from django.utils.crypto import get_random_string
>>> get_random_string(10)
>>> 'PhaWT9X02U'
Something like: 1067603657
And if it is possible, can you recommend an id field generated in this manner?
Add allowed_chars
get_random_string(10, allowed_chars='0123456789')
And to use as id:
def random_id():
random_str = get_random_string(10, allowed_chars='0123456789')
return int(random_str)
And add id field to your model like that:
id = models.IntegerField(primary_key=True, default=random_id, editable=False)
We can extend Reeza's solution by adding a uniqueness constraint to to the field to enable unique behaviour of the ID and by making the generator function aware of database state.
def random_id():
random_int = int(get_random_string(10, allowed_chars='0123456789'))
while YourModel.objects.filter(id=random_int).exists():
random_int = int(get_random_string(10, allowed_chars='0123456789'))
return random_int
and then
id = models.IntegerField(primary_key=True, default=random_id, editable=False, unique=True)
The performance trade-off is minimal for most cases as chances of a collision is minimal and so this function will not be run more than once (there are 10^10 possibilities for this ID).

Django REST Framework serializer with different models

I have three different models that I want to gather in a feed type page. They do all contain different types of things but for simplicity, the models are the same in this instance.
class ObjectA(models.Model):
text = models.TextField()
pub_date = models.DateTimeField('date published',auto_now_add=True)
...
class ObjectB(models.Model):
text = models.TextField()
pub_date = models.DateTimeField('date published',auto_now_add=True)
...
class ObjectC(models.Model):
text = models.TextField()
pub_date = models.DateTimeField('date published',auto_now_add=True)
...
What would be the general idea to serialize lists of all three objects into one list ordered by pub_date using the Django REST Framework. I just have experience using the meta version below but it can only deal with one model I am assuming. Thanks in advance.
class ObjectAListSerializer(serializers.ModelSerializer):
class Meta:
model = ObjectA
fields = [
'text',
'pub_date'
]
Pretty much trying to create something that would work like this:
class AllObjectsListSerializer(serializers.ModelSerializer):
The most important thing here is that you shouldn't be having three different models here. If they are storing the same data, there should be only one model. To have three models means every time you need to execute a statement you need to precede that with a IF ELIF ELSE which is far from DRY. Not to mention the fact that you need to do a UNION as suggested by Wtower in his answer
Merge the models.
You need to make a union on the querysets:
query_union = query1 | query 2
And you need a custom serializer for the fields of that union. Or if the union fields are all the same from any of the models, possibly use those model's modelserializer, but haven't tested that one.
dataA = ASerializer(queryset_A, many=True).data
dataB = BSerializer(queryset_B, many=True).data
dataC = CSerializer(queryset_C, many=True).data
just
return Response(data={'dataA ': dataA , 'dataB ': dataB ,'dataC ': dataC })
If you want return a list,the item is {'a':a,''b':'b,'c':c},you should declare you relation between a,b,c and can filter b and c with a.If so,i will write an example for you.

How to get properties of a model attribute?

Let's say that I have a class such as :
class MyClass(models.Model):
attributeA = models.CharField(max_length=100)
attributeB = models.IntegerField()
attributeC = models.CharField(max_length = 150, blank=True, nullable = True)
attributeD = models.ForeignKey('ModelB',related_name='FK_modelB')
attributeE = models.ManyToManyField('ModelC')
What I want to do is to get the properties of every attribute, not just the name that I got with :
my_instance._meta.get_all_field_name()
(which gave me a list of attributes names). No, what I want is, for every attribute, know what is his type (CharField,IntegerField, ForeignKey, ManyToManyField...), who's related if it's a ForeignKey / ManyToManyField and all the meta data such as max_length and so on.
The aim of it is to serialize a class into a XML and the representation in the XML will be different if it's a ManyToManyField, a ForeignKey or a simple value.
By the way, If anyone know a great class serializer to XML, it would help me a lot !
Thanks for your responses !
Django models _meta.fields is fields list that you can access to get field attributes:
>>> from django.contrib.auth.models import User
>>> u = User.objects.all()[0]
>>> u._meta.fields[1].__class__.__name__
'CharField'
>>> u._meta.fields[1].name
'username'
>>> u._meta.fields[1].max_length
30
>>> u._meta.fields[1].blank
False
# ...
You can get attributes of a specific field by using get_field()
MyClass._meta.get_field('attributeA').max_length

Following users like twitter in Django, how would you do it?

I am playing with relationships in Django/python and I am wondering how you guys would create a relationship between a User and his followers and a Follower to the users he follows.
Would love to read your opinion...
First, you should understand how to store additional information about users. It requires another model that has a relation to one user, the "profile" model.
Then, you could use an M2M field, assuming you'd use django-annoying, you could define your user profile model as such:
from django.db import models
from annoying.fields import AutoOneToOneField
class UserProfile(models.Model):
user = AutoOneToOneField('auth.user')
follows = models.ManyToManyField('UserProfile', related_name='followed_by')
def __unicode__(self):
return self.user.username
And use it as such:
In [1]: tim, c = User.objects.get_or_create(username='tim')
In [2]: chris, c = User.objects.get_or_create(username='chris')
In [3]: tim.userprofile.follows.add(chris.userprofile) # chris follows tim
In [4]: tim.userprofile.follows.all() # list of userprofiles of users that tim follows
Out[4]: [<UserProfile: chris>]
In [5]: chris.userprofile.followed_by.all() # list of userprofiles of users that follow chris
Out[5]: [<UserProfile: tim>]
Also, note that you could check / reuse apps like django-subscription, django-actstream, django-social (harder to use probably)...
You might want to take a look at the django packages for notifications and activities as they all require some follow/subscription database design.
This is how I would do it:
class Tweeter(models.Model):
user = models.ManyToManyField('self', symmetrical=False, through='Relationship')
class Relationship(models.Model):
who = models.ForeignKey(Tweeter, related_name="who")
whom = models.ForeignKey(Tweeter, related_name="whom")
In the shell,
In [1]: t = Tweeter()
In [2]: t.save()
In [3]: f = Tweeter()
In [4]: f.save()
In [5]: r=Relationship()
In [6]: r.who=t
In [7]: r.whom=f
In [8]: r.save()
In [18]: Relationship.objects.all()[0].who.id
Out[18]: 1L
In [19]: Relationship.objects.all()[0].whom.id
Out[19]: 2L
Edit: Makes more sense to use ManyToManyField, as the commenter suggests. Users can have 0-x User followers, and Users can follow 0-x Users.
https://docs.djangoproject.com/en/1.3/ref/models/fields/#manytomanyfield
Without going into code, not much more to be said.

Updating Django model using selected collection of objects

I am learning Django and I refer djangobook.com.
I have a model like this:
class Publisher(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=50)
city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30)
country = models.CharField(max_length=50)
website = models.URLField()
def __unicode__(self):
return self.name
I have populated some data (as described in the demo) and tried to update the value of a particular field of a record (as explained) and this works perfectly fine:
>>> publisher_list = Publisher.objects.all()
>>> p = publisher_list[0]
>>> p.name = u'Apress'
>>> p.save()
But when I try the below (which I assume is equivalent to the above), it does not work. The name is not updated in the database. What am I doing wrong here?
>>> publisher_list = Publisher.objects.all()
>>> publisher_list[0].name = 'Apress'
>>> publisher_list[0].save()
Reference: http://djangobook.com/en/2.0/chapter05/
Thanks.
Your indexing is retrieving the model from the database multiple times. Bind the model to a name before mutating and saving it.
According to QuerySet.__getitem__(), retrieving by item, for example by qs[0], is not cached and will hit DB for each accessing, unless the queryset has been evaluated and thus cache has been filled:
>>> qs = User.objects.all()[:10]
>>> qs[0] is qs[0] # Similar with your issue, you modified the attribute of the first and tried to save the latter.
False
>>> len(qs) # evaluate and fill cache
10
>>> qs[0] is qs[0]
True
So you could
follow Ignacio's answer, or just p = Publisher.objects.all()[0]
or, evaluate the queryset before indexing. Whether this could be easier depends on your code logic.

Categories