Django Custom Group model - django

By default, in django the group model has the name as unique=True. Is it possible to remove this attribute and how? Does it have any major consequence?

It's probably better to prefix the name of the group with something distinctive rather than try to make it non-unique. By default Group.name is used as a natural key by Django, for serialization purposes.
You could work around display issues by doing something during display, like:
def get_group_name(group):
if "|" in group.name:
return group.name.split("|")[1]
return group.name
group = Group.objects.create(name="COMPANY_X|Sales")
print(get_group_name(group))
# Sales
You can still define your own Group model but it would require customizing your user model quite significantly, which is a lot of work, and there may still be things that rely on Group name uniqueness in Django internals.

Related

Django annotate single instance of many-2-many field using 'when' on other fields of the instnce

I have a somewhat complicated model, so I will do my best to give an example that simplifies my current state, and my need.
I have a queryset:
qs = MyModel.objects.all()
Each instance in this queryset, has a many-2-many field to another model, let's call it 'First_M2M'. First_M2M has a foreign key to another model, and a many-2-many to yet another model (FkModel and Second_M2M, respectively):
qs[0].first_m2m.fk_model.name # This is a string.
qs[0].first_m2m.second_m2m.all() # This is a many2many manager.
The Second_M2M has another many-2-many relationship, Third_M2M:
qs[0].first_m2m.second_m2m[0].third_m2m.all() # Also a m2m manager.
Now that's what I'm trying to do: I want to order my qs, based on a value from one of the second_m2m instances. However, I need to choose which instance is it, and this is done by querying a field in the fk_model (to determine the first_m2m instance) AND a field in one of the instances in the third_m2m (this will determine the second_m2m).
In order to make it even more interesting, the value to order by, is YAML.
Here's what I tried to do:
qs.annotate(val_to_filter_by=Case(
When(
first_m2m__fk_model__name='foo',
first_m2m__second_m2m__third_m2m__some_field='bar'),
then='first_m2m__second_m2m__value_field',
default=Value(None),
output_field=YAMLField()
)
).order_by(val_to_filter)
I believe what I got wrong is the querying, that is not coherent enough for Django to determine which instance it should take. But I can't find my problem.
Any help will be much appreciated.
Solved it...
I had a mistake with my 'then. It was part of my 'Case' instead of the 'When'. Here is the solution:
qs.annotate(val_to_filter=Case(
When(
first_m2m__fk_model__name='foo',
first_m2m__second_m2m__third_m2m__some_field='bar',
then=F('first_m2m__second_m2m__value_field')
),
default=Value(''),
output_field=YAMLField()
)).order_by(val_to_filter)
UPDATE: Didn't solve it...
Although I got the right query, and it worked, I am getting ALL instances of the second_m2m, instead of a single instance. Still not sure how to get exactly what I need, seems like Django is not my friend in this case.
UPDATE2:
Changed 'default=None' and added
.exclude(val_to_filter=None)
right before the ordering. Seems to work...

Best approach to model contacts in the Django modelling language

I am designing a contact relationship application that needs to store contacts in groups. Basically I have 7 "group types" (simplified it to 3 for my image), each group type shares the same fields so I thought that it would make sense to use an abstract "group", and let all group types inherit the methods from this abstract group.
So this is basically the idea:
However, this approach results in a couple of unexpected difficulties. For example:
I am not able to use a foreignkey of an abstract class, so if I would want to model a relationship between a group and a contact, I have to use the following approach:
limit = (models.Q(app_label='groups', model="Group type A") |
models.Q(app_label='groups', model="Group type B") |
models.Q(app_label='groups', model="Group type C")
)
group_type = models.ForeignKey(ContentType, limit_choices_to=limit)
group_id = models.PositiveIntegerField()
group = GenericForeignKey('group_type', 'group_id')
This seems quite hacky, and with this approach I am forced to do some hard coding as well. I am not able to call all groups with a simple query, maybe a new group will be added in the future.
Is there a better approach to model a relationship like this? Am I using the abstract class completely wrong?
Edit: some extra explanation in response to the questions.
A user is connected to a group with another object called "WorkRelation", because there is some extra data that is relevant when assigning a user to a group (for example his function).
I initially went for an abstract class because I thought that this would give me the flexibility to get all Group types be just calling Group.objects.all(). If I would use a base model, the groups aren't connected and I will also have to hard-code all group names.
Since your child models do not have additional fields, you can make them proxy models of the base group model. Proxy models do not create new database tables, they just allow having different programmatic interfaces over the same table.
You could then define your ForeignKey to the base group model:
group = ForeignKey(BaseGroup)
Use django-polymodels or a similar app to have the groups casted to the right type when queried.
More on model inheritance in the doc.
Why don't use solid base model instead of abstract model? Then you just put contacts as either ForeignKey or ManyToMany to the base model.

Django GROUP BY including unnecessary columns?

I have Django code as follows
qs = Result.objects.only('time')
qs = qs.filter(organisation_id=1)
qs = qs.annotate(Count('id'))
And it gets translated into the following SQL:
SELECT "myapp_result"."id", "myapp_result"."time", COUNT("myapp_result"."id") AS "id__count" FROM "myapp_result" WHERE "myapp_result"."organisation_id" = 1 GROUP BY "myapp_result"."id", "myapp_result"."organisation_id", "myapp_result"."subject_id", "myapp_result"."device_id", "myapp_result"."time", "myapp_result"."tester_id", "myapp_result"."data"
As you can see, the GROUP BY clause starts with the field I intended (id) but then it goes on to list all the other fields as well. Is there any way I can persuade Django not to specify all the individual fields like this?
As you can see, even with .only('time') that doesn't stop Django from listing all the other fields anyway, but only in this GROUP BY clause.
The reason I want to do this is to avoid the issue described here where PostgreSQL doesn't support annotation when there's a JSON field involved. I don't want to drop native JSON support (so I'm not actually using django-jsonfield). The query works just fine if I manually issue it without the reference to "myapp_result"."data" (the only JSON field on the model). So if I could just persuade Django not to refer to it, I'd be fine!
only only defers the loading of certain fields, i.e. it allows for lazy loading of big or unused fields. It should generally not be used unless you know exactly what you're doing and why you need it, as it is nothing more than a performance booster than often decreases performance with improper use.
What you're looking for is values() (or values_list()), which actually excludes certain fields instead of just lazy loading. This will return a dictionary (or list) instead of a model instance, but this is the only way to tell Django to not take other fields into account:
qs = (Result.objects.filter_by(organisation_id=1)
.values('time').annotate(Count('id')))

Groups per object using Django and django-guardian object permissions

I'm currently creating a structure where I have employees which belong to a company.
Within this company I need to be able to create several groups. Ranks if you will. You could assign less permissions to lower ranks and more permissions to higher ranks.
I want to go for object level permissions and I noticed the django-guardian project gave me exactly what I needed. It works with the native User and Group objects so I'm now trying to find a way to implement the native group object in a company object.
Problems I face is that name in group is unique. So if 2 companies add the same group, errors will occur.
I found an implementation that works in a way but seems quite 'hacky' to me. In my company I declared a group variable that references Group:
class Company(models.Model):
...
groups = models.ManyToManyField(Group, through='CompanyRole')
CompanyRole basically houses the group name and a reference to company and group
class CompanyRole(models.Model):
group = models.ForeignKey(Group)
company = models.ForeignKey(Company)
real_name = models.CharField(max_length=60, verbose_name=_('Real name'))
objects = CompanyGroupManager()
I created a custom manager with a convenient method to add a new 'company group'
class CompanyGroupManager(models.Manager):
def create_group(self, company, group_name):
un_group_name = str(company.id) + '#' + group_name
group = Group.objects.create(name=un_group_name)
company_group = self.model(
real_name=group_name,
company=company,
group=group
)
company_group.save(using=self._db)
return company_group
Here's the part I don't really feel confortable about. In order to change the problem with the unique name on the Group model I used a combination of the company id, a hash sign and the actual group name to avoid clashes.
Now my question is: are there better methods in my scenario, am I missing something or is this a good way of accomplishing what I need?
Unfortunately there is no way of getting around the unique requirement, that is because this field is used as the id:
https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.Field.unique
Your options are the following:
1) Mocking the model.
You would basically just create a new Group model that doesn't have the unique requirement. The downside here is that you'd need to use it everywhere, so if this requires updating 3rd party apps, it might not be worth it.
2) make the name you unique. (As you did)
Make sure that you document your convention well, so that all future coders will know what they are looking at.Something like "company name"#"group name" could make more intuitive sense than an id. If the a hash might appear in either then use a more certain delimiter ("__" is a relatively common way of connecting related concepts in django, I might go for this).
I would recommend that you add the following to make it easy for you to access the name.
def get_name(self):
# Explain how you get the group name from your uniqueified name
return self.name.split('#')[1]
Group.add_to_class('get_name', get_name)
When you access your group's name in your app, just do:
my_group.get_name()
You might also want to put the generating the uniqueified name into an overridden version of the save(). This would give you nicer split between model and view...

Generating a single queryset with filtered summary data across a foreign key?

I have a small Django project to learn with (it's a web UI for the RANCID backup software) and I've run into a problem.
The model for the app defines Devices, and DeviceGroups. Each Device is a member of a group and has a couple of state flags - Enabled, Successful - to indicate if they are operating correctly. Here's the relevant bits.
class DeviceGroup(models.Model):
group_name = models.CharField(max_length=60,unique=True)
class Device(models.Model):
hostname = models.CharField(max_length=60,unique=True)
enabled = models.BooleanField(default=True)
device_group = models.ForeignKey(DeviceGroup)
last_was_success = models.BooleanField(default=False,editable=False)
I have a summary table on the front 'dashboard' page, that shows a list of all the groups, and for each group, how many devices are in it. I'd like to also show the number of Active devices, and the number of failing (i.e. Not last_was_success) devices per-group. The plain device count is already available through the ForeignKey field.
This seems like the kind of thing that annotate is for, but not quite. And actually, I'm not sure how I'd do it with raw SQL either. Most likely as three queries and some lookup afterwards, or subqueries.
So - is it possible 'nicely' in Django? Or alternatively, how do you do the joining up again in the Template or View? The object passed into the template is simply:
device_groups = DeviceGroup.objects.order_by('group_name')
currently, and I don't think I can just add extra fields onto the queryset results "manually", can I? i.e. it's not a dict or similar.
i think you must use
device_groups = DeviceGroup.objects.all().order_by('group_name')
or
device_groups = DeviceGroup.objects.filter(condition).order_by('group_name')