django with mysql model string array field - django

I need to design a model Card that should expect a request like following:
{"thing":"Book","responsibilities":["Name","ISBN"],"collaborators":[""]}
So far I designed my model like following:
class Responsibility(models.Model):
name = models.CharField(max_length=50, blank=True, null = True, )
class Collaborator(models.Model):
name = models.CharField(max_length=50, blank=True, null = True, )
class Card(models.Model):
thing = models.CharField(max_length=50, blank=True, null = True, )
responsibilities = models.ForeignKey(Responsibility, related_name='res_cards', blank=True, )
collaborators = models.ForeignKey(Collaborator, related_name='col_cards', blank=True, )
but somehow having name attribute on other two models seems unnecessary for such a simple case. Can I design my model better to accept request like above?

Sorry to say this, but Django doesn't have ArrayField when if DB is MySql.
It has ArrayField if DB is Postgresql.
Here you can see the docs

Related

Django convert raw SQL query to Django ORM

I am using Django Rest Framework and I have this query in raw SQL but I want to do it in the Django ORM instead.
I have tried using the different Django tools but so far it has not given me the expected result.
select tt.id, tt.team_id, tt.team_role_id, tt.user_id from task_teammember tt
inner join task_projectteam tp on tp.team_id = tt.team_id
where tp.project_id = 1
models
class TeamMember(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
team = models.ForeignKey(Team, on_delete=models.CASCADE)
team_role = models.ForeignKey(TeamRole,on_delete=models.CASCADE)
state = models.IntegerField(default=1)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(default=None, null=True)
class ProjectTeam(models.Model):
project = models.ForeignKey(Project, on_delete=models.CASCADE, blank=True, null=True)
team = models.ForeignKey(Team, on_delete=models.CASCADE, blank=True, null=True)
state = models.IntegerField(default=1)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(default=None, null=True)
If you have a project object already then this should get you what I believe you want. Your TeamMember model has access to Team, which links to ProjectTeam, and to Project - the double-underscore accessor navigates through the relationships.
TeamMember.objects.filter(team__projectteam__project=project)
I would advise to span a ManyToManyField over the ProjectTeam, this will make queries simpler:
from django.conf import settings
class TeamMember(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
updated_at = models.DateTimeField(auto_now=True)
# …
class Team(models.Model):
projects = models.ManyToManyField(Project, through='ProjectTeam')
# …
class ProjectTeam(models.Model):
# …
updated_at = models.DateTimeField(auto_now=True)
Then you can easily filter with:
TeamMember.objects.filter(team__projects=project_id)
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: Django's DateTimeField [Django-doc]
has a auto_now=… parameter [Django-doc]
to work with timestamps. This will automatically assign the current datetime
when updating the object, and mark it as non-editable (editable=False), such
that it does not appear in ModelForms by default.
I think it's goes like:
TeamMember.objects.filter(team__projectteam__project__id=1)
Django orm allow reverse foreginkey lookup

Django Models relation with primary key add extra "_id" to the column

These are my two models, when I try to open City page on Django I get an error: "column city.country_id_id does not exist". I don't know why python adds extra _id there.
class Country(models.Model):
country_id = models.CharField(primary_key=True,max_length=3)
country_name = models.CharField(max_length=30, blank=True, null=True)
class Meta:
managed = False
db_table = 'country'
class City(models.Model):
city_id=models.CharField(primary_key=True,max_length=3)
city_name=models.CharField(max_length=30, blank=True, null=True)
country_id = models.ForeignKey(Country, on_delete=models.CASCADE)
class Meta:
managed = False
db_table = 'city'
Because if you construct a foreign key, Django will construct a "twin field" that stores the primary key of the object. The foreign key itself is thus more a "proxy" field that fetches the object.
Therefore you normally do not add an _id suffix to the ForeignKey:
class City(models.Model):
city_id = models.CharField(primary_key=True,max_length=3)
city_name = models.CharField(max_length=30, blank=True, null=True)
country = models.ForeignKey(Country, on_delete=models.CASCADE)
class Meta:
managed = False
db_table = 'city'
It however might be better for unmanaged tables, to specify a db_column=… parameter [Djang-doc] in the ForeignKey:
class City(models.Model):
city_id = models.CharField(primary_key=True,max_length=3)
city_name = models.CharField(max_length=30, blank=True, null=True)
country = models.ForeignKey(Country, db_column='country_id', on_delete=models.CASCADE)
class Meta:
managed = False
db_table = 'city'
With this parameter you make it explicit how the column is named at the database side.
this is due to Django's behind the scenes magic.
The fields documentation is very clear about that and I highly recommend you read the Foreign Key section in the link below:
https://docs.djangoproject.com/en/3.0/ref/models/fields/#django.db.models.ForeignKey
Basically, when you want to access the Country reference in the if a City instance, you would do it like this:
city.country_id
I also recommend another naming convention for your Foreign Key fields. Instead of <modelname>_id = models.ForeignKey... just call it <modelname> = models.ForeignKey...
Hope this helps, happy coding

Django ORM annotate on foreign key query

I have 3 models:
class Project(models.Model):
name = models.CharField(max_length=300, unique=True)
description = models.CharField(
max_length=2000,
blank=True,
null=True,
default=None
)
class QuestionSession(models.Model):
name = models.CharField(
max_length=500, default=None, null=True, blank=True
)
project = models.ForeignKey(
Project,
on_delete=models.CASCADE,
related_name='sessions',
blank=True,
null=True,
default=None
)
class Question(models.Model):
description = models.CharField(max_length=500)
question_session = models.ForeignKey(
QuestionSession,
on_delete=models.CASCADE,
related_name='questions',
blank=True,
null=True,
default=None
)
As you can see, Project contains Sessions, Session contains questions.
What I'm trying to achieve is I wanna fetch a single Project with sessions and number of questions in them. I can do it easily with 2 different queries but I cannot do it in 1 query.
My serializers:
class SingleProjectSerializer(serializers.ModelSerializer):
sessions = MinifiedSessionSerializer(many=True)
class Meta:
model = Project
fields = [
'id',
'name',
'description',
'sessions'
]
class MinifiedSessionSerializer(serializers.ModelSerializer):
questions_number = serializers.IntegerField()
class Meta:
model = QuestionSession
fields = [
'id',
'name',
'questions_number'
]
I used to grab sessions in a single query like this:
Project.objects.get(id=project_id).sessions.annotate(questions_number=Count('questions'))
But how to do it now? I need to fetch the project first and then annotate on sessions. I have no idea how to do it. I need a query like this:
Project.objects.filter(pk=project_id).annotate(sessions__questions_number=Count('sessions__questions'))
I don't believe it's possible through Django ORM. The only solution which I can think of is changing the way on how you ask for the data:
sessions = QuestionSession.objects.filter(project_id=project_id).select_related('project').annotate(questions_number=Count('questions'))
project = sessions[0].project
That would end up in a single query, but I assume you want to pass this project instance down to the DRF serializer. In such case the project knows nothing about prefetched sessions, so it would need to be handled separately. Additionally there is an issue when particular project has no associated sessions - sessions[0].project would raise exception. To keep the code clean I would probably stay with yours current approach (but then - the issue remains unresolved to keep everything in one db hit).

Django: Converting a GenericForeignKey Relationship to a ForeignKey Relationship

I'm working to remove an existing GenericForeignKey relationship from some models. Id like to change it to the Reformatted Model below. Does migrations provide a way to convert the existing content_type and object_ids to the respective new ForeignKey's? (to keep existing data). Basically brand new at programming, so pardon me if I'm asking a stupid question.
class Donation(models.Model):
amount_id = models.CharField(max_length=12, unique=True, editable=False)
date_issued=models.DateField(auto_now_add=True)
description=models.TextField(blank=True, null=True)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type','object_id')
class Individual(BaseModel):
first_name = models.CharField(max_length=50)
middle_name = models.CharField(max_length=50, blank=True,
null=True)
last_name = models.CharField(max_length=50)
suffix = models.CharField(max_length=50, blank=True, null=True)
contributor = generic.GenericRelation(Donation, related_query_name='individual')
class Organization(models.Model):
name = models.CharField(max_length=100)
contributor = generic.GenericRelation(Donation, related_query_name='organization')
Reformatted Model
class Donation(models.Model):
amount_id = models.CharField(max_length=12, unique=True, editable=False)
date_issued=models.DateField(auto_now_add=True)
description=models.TextField(blank=True, null=True)
contributor_group = models.ForeignKey(Organization, null=True, blank=True, on_delete=models.CASCADE)
contributor_individual = models.ForeignKey(Individual, null=True, blank=True, on_delete=models
Based on your model definition of Donation Model, one of fields contributor_group , contributor_model will always be Null post migration.
I hope you have taken that into you consideration.
Just to be safe Do this in two phases.
1. Keep the content_type and object_id and add the two new fields.
2. Next step remove the generic fields post data population.
There are two ways to populate those new fields
Django migrations provides you a way to populate new fields with values during the migrations. You can look it up. I haven't done that before either.
For more control and some learning as well. You can populate that via scripting as well. You can setup django-extensions module in your project. And write a script to do that population for you as well. A sample script would look like.
from myproject.models import Donation, Individual, Organization
from django.contrib.contenttypes.models import ContentType
def run():
organization_content_type = ContentType.objects.get_for_model(Organization)
individual_content_type = ContentType.obejcts.get_for_model(Individual)
donations = Donation.objects.all()
for donation in donations:
if donation.content_type_id == organization_content_type.id:
donation.contributor_group = donation.object_id
elif donation.content_type_id == individual_content_type.id:
donation.contributor_individual = donation.object_id
else:
print "Can't identify content type for donation id {}".format(donation.id)
donation.save()
Check the values are correct and then remove the generic fields.
Facing some issues with formatting here.

Django models: Why the name clash?

Firstly, I know how to fix the problem, I'm just trying to understand why it's occuring. The error message:
users.profile: Reverse query name for field 'address' clashes with related field 'Address.profile'. Add a related_name a
rgument to the definition for 'address'.
And the code:
class Address(models.Model):
country = fields.CountryField(default='CA')
province = fields.CAProvinceField()
city = models.CharField(max_length=80)
postal_code = models.CharField(max_length=6)
street1 = models.CharField(max_length=80)
street2 = models.CharField(max_length=80, blank=True, null=True)
street3 = models.CharField(max_length=80, blank=True, null=True)
class Profile(Address):
user = models.ForeignKey(User, unique=True, related_name='profile')
primary_phone = models.CharField(max_length=20)
address = models.ForeignKey(Address, unique=True)
If I understand correctly, this line:
address = models.ForeignKey(Address, unique=True)
Will cause an attribute to be added to the Address class with the name profile. What's creating the other "profile" name?
What if I don't need a reverse name? Is there a way to disable it? Addresses are used for a dozen things, so most of the reverse relationships will be blank anyway.
Is there a way to copy the address fields into the model rather than having a separate table for addresses? Without Python inheritance (this doesn't make sense, and if an Model has 2 addresses, it doesn't work).
in the django docs it says:
If you'd prefer Django didn't create a backwards relation, set related_name to '+'. For example, this will ensure that the User model won't get a backwards relation to this model:
user = models.ForeignKey(User, related_name='+')
but I never tried it myself....
I'm not sure where the errant profile field is coming from… But one way to find out would be: temporary remove address = models.ForeignKey(…) from Profile, ./manage.py shell, from ... import Address then see what Address.profile will tell you.
I don't think there is any official way to inherit only the fields from some other Model without using inheritance… But you could fake it like this (where SourceModel is, eg, Address and TargetModel is, eg, Profile):
for field in SourceModel._meta.fields:
TargetModel.add_to_class(field.name, copy.deepcopy(field))
(this is coming from Django's ModelBase __new__ implementation)
I don't think it's possible to disable the reverse name.
I've just done a quick grep over the code and it doesn't look like there is any logic which will bypass setting up the related_name field on the related model.
For Example: Add just '+'
class GeneralConfiguration(models.Model):
created_at = models.DateTimeField(editable=False, default=settings.DEFAULT_DATE)
updated_at = models.DateTimeField(editable=False, default=settings.DEFAULT_DATE)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.PROTECT, related_name='+')
updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.PROTECT, related_name='+')