Django Foreign key to not loaded object - python-3.x

Hello I have a problem that I want to link foreign key to model's related object in other words I want to link foreign key to not loaded object's fields.
class Category(models.Model):
...
filter_option_content_type = models.ForeignKey(ContentType, on_delete=models.SET_NULL, limit_choices_to=(
models.Q(app_label='catalog', model='FrameSize') |
models.Q(app_label='catalog', model='ShoeSize') |
models.Q(app_label='catalog', model='ClothSize')
), null=True)
...
class Product(models.Model):
...
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
...
class ProductItem(models.Model):
...
model = models.ForeignKey(Product, verbose_name='Модель', on_delete=models.CASCADE, related_name='productitem')
size = models.ForeignKey('model.category.filter_option_content_type', on_delete=models.SET_NULL, null=True)
...
And sure i got this error:
ValueError: Invalid model reference 'model.category.filter_option_content_type'. String model references must be of the form 'app_label.ModelName'
is it possible to do relation like this wtihout using GenericForeignKey instead of ForeignKey?

I think that you don't really need to add an additional relation since you can access the field you want via Product:
item = ProductItem()
size = item.model.category.filter_option_content_type
In case you'd like to query using this field, it would look like:
item = ProductItem.objects.filter(
model__in=Product.objects.filter(
category__in=Category.objects.filter(
filter_option_content_type='<your desired value of size>'
)
)
)
Since the relations are based on Foreign Keys which are indexed - such query shouldn't have a noticeable effect on performance (despite that it may seem a bit awkward)

Related

How to handle foreign-keys during the iteration through attributes of Django model (Python)?

Dear Django/Python experts. I have a Django model (python class) which contain standard fields and also fields represented by foreign keys. It is easy to iterate throught attributes of a model however I have no idea how to handle foreign keys?
Here is a model nr.1 Employee containing foreign key which refers to another model EmployeeLocation:
class Employee(models.Model):
firstname = models.CharField(max_length=128)
lastname = models.CharField(max_length=128)
location = models.ForeignKey(EmployeeLocation, on_delete=models.CASCADE)
and here is a model nr.2 EmployeeLocation:
class EmployeeLocation(models.Model):
id = models.BinaryField(primary_key=True, max_length=16, null=False)
city = models.CharField(max_length=32)
and now I iterate via attributes of Employee in the following way:
# Collecting names of class fields.
field_names = [f.name for f in Employee._meta.get_fields()]
for current_attribute in field_names:
field_value = str(getattr(my_employee, current_attribute))
This solution works fine for standard attributes but does not return values when reaches the location which is a foreign key.
To tackle this issue I did the following stunts :) :
I have made a dictionary containing names of foreign keys and as values I have placed Django queryset, that gets a value - but this is not an elegant hack :) In this way then iteration ecounters attribute which is foreign-key, it takes value from dictionary (which is generated by queryset):
FKs = {'location': EmployeeLocation.objects.filter(id=my_employee.location_id)[0].city,
...
...}
# Collecting names of class fields.
field_names = [f.name for f in Employee._meta.get_fields()]
for current_attribute in field_names:
if current_attribute in FKs.keys():
field_value = FKs[current_attribute]
else:
field_value = str(getattr(my_employee, current_attribute))
Please tell me in simple way how shall I realize it properly. Thank you so much in advance :)

ManytoManyField Django : how to call models in methods?

I need help for something, I want to call models with ManyToManyField.
I want to have method to get Class A from Class B, and another in Class B to get Class A.
here's my (shortened) code :
class Licence(models.Model):
name = models.CharField(max_length=64)
picture = models.ImageField(upload_to='finder/static/finder/img/licence/',null=True, blank=True)
description = models.TextField(null=True, blank=True)
#returns a list of games from this license
def getGamesOnThisLicence(self):
#i don't know how to proceed
class Game(models.Model):
name = models.CharField(max_length=64)
description = models.TextField()
release_date = models.DateField(null=True, blank=True)
licence = models.ManyToManyField(Licence, blank=True, null=True)
#return name of licence to which the game belongs
def getLicenceName(self):
return self.licence.name
You can access the Games with:
my_license.game_set.all()
so you can use self in the getGamesOnThisLicense, but probably there is not much gain to define a function since this makes accessing the Games already quite convenient.
Perhaps you however want to transform the ManyToManyField into a ForeignKey to License since self.license.name makes not much sense: for a ManyToManyField, self.license is a Manager over License objects that can manage zero, one or more Licenses, so you can not use self.license.name.

Merge two redundant models in Django

I'm working on an online shop at the moment. The shop is written in Django, and was programmed by another person, so I spent days trying to make sense of what he did. At the moment the shop sells only two articles, on two different pages (meaning they have to be bought separately), so the shop is heavily oriented towards this situation. The problem is, the shop's owner expressed his interest in selling everything in one page in the near future, and in adding more products. And that's where my problems start.
The model structure was something like this (extremely simplified):
class Category1(BaseModel):
name = models.CharField(max_length=32)
class Category2(BaseModel):
name = models.CharField(max_length=64)
class Price1(BaseModel):
category = models.ForeignKey(Category1, on_delete=models.CASCADE)
price = models.DecimalField(max_digits=16, decimal_places=2)
currency = models.CharField(max_length=3)
class Price2(BaseModel):
category = models.ForeignKey(Category2, on_delete=models.CASCADE)
price = models.DecimalField(max_digits=16, decimal_places=2)
currency = models.CharField(max_length=3)
class Order1(BaseModel):
[personal information fields]
class Order2(BaseModel):
[personal information fields]
class Article1(BaseModel):
price = models.ForeignKey(Price1, on_delete=models.CASCADE)
order = models.ForeignKey(Order1, on_delete=models.CASCADE, related_name='articles')
class Article2(BaseModel):
price = models.ForeignKey(Price2, on_delete=models.CASCADE)
order = models.ForeignKey(Order2, on_delete=models.CASCADE, related_name='articles')
There is much more than this, of course, but this should be enough to show the relationships between the models. The complete structure of course makes more sense than this one. BaseModel is a class that contains an ID, creation time and last edit.
I managed to put all the common elements into abstract classes BaseCategory, BasePrice, BaseOrder and BaseArticle, but this is not enough if I want to really expand the shop. Finishing this work is just a matter of time and patience, but how should I proceed once I'm in this situation?
class BaseCategory(BaseModel):
name = models.CharField(max_length=64)
class Meta:
abstract = True
class Category1(BaseCategory):
pass
class Category2(BaseCategory):
pass
class BasePrice(BaseModel):
price = models.DecimalField(max_digits=16, decimal_places=2)
currency = models.CharField(max_length=3)
class Meta:
abstract = True
class Price1(BasePrice):
category = models.ForeignKey(Category1, on_delete=models.CASCADE)
class Price2(BasePrice):
category = models.ForeignKey(Category2, on_delete=models.CASCADE)
class BaseOrder(BaseModel):
[personal information fields]
class Meta:
abstract = True
class Order1(BaseOrder):
pass
class Order2(BaseOrder):
pass
class BaseArticle(BaseModel):
class Meta:
abstract = True
class Article1(BaseArticle):
price = models.ForeignKey(Price1, on_delete=models.CASCADE)
order = models.ForeignKey(Order1, on_delete=models.CASCADE, related_name='articles')
class Article2(BaseArticle):
price = models.ForeignKey(Price2, on_delete=models.CASCADE)
order = models.ForeignKey(Order2, on_delete=models.CASCADE, related_name='articles')
I need to get rid of the specific classes completely, otherwise when I will add new articles, I will have to create new classes, and this is not a scalable solution.
My problems are the following:
How do I get rid of the empty specific classes like Price1 or Order1 without losing any information? I know I will have to get rid of the abstract variable, but I don't know what to do next.
How do I manage the foreign keys in the remaining classes? I'm experimenting a bit with GenericForeignKey at the moment, and this would probably let me move the declarations into the base classes, but I'm not sure if changing a definition will reset all fields.
Just to be clear, the shop is already up and running. We can't stop it, and we can't lose data. We sell services, so the customers have to be able to access their products even long after the purchase.
Thanks in advance for your interest and your time.
To keep this answer short we will only discuss one model here. In this instance Category. Firstly add a new model Category (keep your other models for now):
class Category(BaseModel):
name = models.CharField(max_length=64)
Next run makemigrations this would generate a migration file to make this new table in the database. After this you need to make a Data Migration [Django docs] to copy the data from the other two tables that you have.
To do this first run:
python manage.py makemigrations --empty yourappname
This will generate a migration file that does nothing for now. We will edit this migration file and add some code to copy the data from your other tables to this new table. In the end your migration file would look something like:
from django.db import migrations
def populate_category(apps, schema_editor):
Category1 = apps.get_model('yourappname', 'Category')
Category2 = apps.get_model('yourappname', 'Category')
Category = apps.get_model('yourappname', 'Category')
# add all fields except the pk in values(), i.e. values('field1', 'field2')
for category in Category1.objects.values('name'):
Category.objects.create(**category) # Add some field indicating this object is of Category1 if needed
for category in Category2.objects.values('name'):
Category.objects.create(**category) # Add some field indicating this object is of Category2 if needed
class Migration(migrations.Migration):
dependencies = [
('yourappname', '0001_initial'),
]
operations = [
migrations.RunPython(populate_category, reverse_code=migrations.RunPython.noop),
]
Now you can simply run python manage.py migrate and you would have a new table Category which has all the data from Category1 and Category2. (This might take some time if there are many rows). After this you can remove the models Category1 and Category2 and migrate again to remove those tables.
Note: Perform these operations carefully, and make sure you have got the data properly in the new table before deleting the old ones.
Refer the documentation linked above for more information on
migrations. (Test this on a local development server before doing it
on production to be safe)

PostGIS geography does not support the "~=" function/operator

I am trying to save point field in the database via update_or_create method but it is giving me this error.
My function to save data:
for city in cities_data:
obj, created = models.City.objects.update_or_create(
name = city["name"],
country = city["country"],
state = city["state"],
point = Point(
float(city["longitude"]),
float(city["latitude"])
),
radius = city["radius"],
is_curated = city["is_curated"],
is_metro_city = city["is_metro_city"]
)
obj.save()
Model:
class City(models.Model):
name = models.CharField(max_length=50)
country = models.CharField(max_length=50)
state = models.CharField(max_length=50)
point = gis_models.PointField(geography=True, null=True)
is_metro_city = models.BooleanField(default=False)
is_curated = models.BooleanField(default=False)
radius = models.IntegerField(null=True)
when I try to run this I get this error:
ValueError: PostGIS geography does not support the "~=" function/operator.
I want to know why I am getting this error, I did not find any useful information related to this. Thanks in Advance.
If you want to use this you can use it like this:
obj, created = models.City.objects.update_or_create(
**city_data,
defaults={'point': Point(
float(city["longitude"]),
float(city["latitude"])
)}
)
I do not know why it does not work that way, but I made it work this way, if you find it out why it does not work you can improve the answer.
I have the same error and the problem was that I have a unique together constraint in model's class Meta eg:
class Meta:
unique_together = 'name', 'geom'
After deleting it, the error disappears although you must check by yourself if you need such constraint.

creating a Field based on the data in choice field in django

I am trying to write a program for registering plates. However we have three types of plate and i've created a choice field for these three. the thing i wanna do is that i need to create a OneToOne field which it's given model is based on the data of the choice filed for example if the user chose 1 i need to have OnetoOne field to CarPlate if 2 OneToOne field to MotorPlate and so on....
VEHICLE_CHOICES = (
("1", "سواری ملی"),
("2", "سواری منظقه ازاد انزلی"),
("3", "موتور سیکلت"),)
class Vehicle(models.Model):
vehicle_type = models.CharField(
max_length=3,
choices=VEHICLE_CHOICES,
blank=False
)
# below is an example of what i want to do
if vehicle_type == 1 :
plate_car = models.OneToOneField(CarPlate, on_delete=models.CASCADE, related_name="savari", blank=True)
elif vehicle_type == 2:
plate_anzali = models.OneToOneField(AnzaliPlate, on_delete=models.CASCADE, related_name="mantaqe", blank=True)
else:
plate_motor = models.OneToOneField(MotorPlate, on_delete=models.CASCADE, related_name="motor", blank=True)
the code above works but it doesn't give me the right answer.
Apparently these kind of settings are not allowed in models, but you can write a global field for every one of them and then in the front-end part of the project you can only get the fields that you want for the chosen choice field

Resources