Related
I am trying to modify the way I want to accept data from endpoints without modifying the model defined for it. Hence I want to modify the serializer. I am not sure how should I redefine it.
I have a serializer defined as
class AssetWorkorderSerializer(MaxenBaseSerializer):
id = ReadOnlyField()
assets = PrimaryKeyRelatedField(
many=True,
read_only=False,
allow_null=False,
queryset=models.Asset.objects.all(),
)
assignee = PrimaryKeyRelatedField(
many=False, read_only=False, allow_null=True, queryset=User.objects.all()
)
checklists = PrimaryKeyRelatedField(
many=True, read_only=False, queryset=Checklist.objects.all()
)
assigned_by = PrimaryKeyRelatedField(
many=False, queryset=User.objects.all())
deadline = ReadOnlyField()
overdue = ReadOnlyField()
class Meta:
model = models.AssetWorkorder
fields = "__all__"
So objects accepted are:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"url": "https://<some-url>/assetworkorders/29/",
"id": 29,
"assets": [
292,
293
],
"assignee": 33,
"checklists": [
7,
8,
9
],
"assigned_by": 32,
"deadline": "2022-03-31",
"overdue": true,
"created": "2022-03-31T03:52:59.123745Z",
"updated": "2022-03-31T17:18:49.599963Z",
"name": "Service HVACs",
"description": "Service HVACs",
"status": "OP",
"priority": "LO",
"duedate": "2022-03-31",
"scheduleddate": "2022-03-30",
"notifications_enabled": false
},
]
}
Instead I want to accept like this:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"url": "https://<some-url>/assetworkorders/29/",
"id": 29,
"assetChecklist": [
{"asset": 292, "checklists": [7,8]},
{"asset": 293, "checklists": [8,9]},
],
"assignee": 33,
"assigned_by": 32,
"deadline": "2022-03-31",
"overdue": true,
"created": "2022-03-31T03:52:59.123745Z",
"updated": "2022-03-31T17:18:49.599963Z",
"name": "Service HVACs",
"description": "Service HVACs",
"status": "OP",
"priority": "LO",
"duedate": "2022-03-31",
"scheduleddate": "2022-03-30",
"notifications_enabled": false
},
]
}
Without modifying model.
I am already modifying create method, I will overwrite it again to save data as it is.
Other models are defined as:
class AssetWorkorder(MaxenBaseModel):
#property
def organizationsite_name(self):
return self.assets[0].organizationsite.organization.name
assets = models.ManyToManyField("Asset", verbose_name="Assets")
name = models.CharField("Name", blank=False, max_length=255)
description = models.CharField(
"Description", null=True, blank=True, max_length=2048
)
class Status(models.TextChoices):
OPEN = "OP", "Open"
INPROGRESS = "IP", "In progress"
BLOCKED = "BL", "Blocked"
COMPLETED = "CO", "Completed"
status = models.CharField(
max_length=2, choices=Status.choices, default=Status.OPEN)
class Priority(models.TextChoices):
LOW = "LO", "Low"
NORMAL = "NO", "Normal"
HIGH = "HI", "High"
URGENT = "UR", "Urgent"
priority = models.CharField(
max_length=2, choices=Priority.choices, default=Priority.NORMAL
)
duedate = models.DateField("Due date", editable=True, null=True)
scheduleddate = models.DateField(
"Scheduled date", editable=True, null=True)
assigned_by = models.ForeignKey(
"auth.User", help_text="", on_delete=models.CASCADE, related_name="assigned_by"
)
assignee = models.ForeignKey(
"auth.User",
help_text="",
on_delete=models.CASCADE,
related_name="assignee",
null=True,
)
#property
def deadline(self):
"""
Deadline is the scheduled date if this one is upcoming, otherwise,
the deadline is the due date.
"""
if self.duedate and self.scheduleddate:
return (
self.scheduleddate
if self.scheduleddate >= timezone.now().date()
else self.duedate
)
else:
return self.duedate or self.scheduleddate
#property
def overdue(self) -> bool:
return self.deadline == self.duedate
notifications_enabled = models.BooleanField(
"Enable notifications", default=False)
checklists = ManyToManyField("Checklist", through="WorkorderChecklist")
class Meta:
ordering = ["id"]
verbose_name = "Workorder (class: AssetWorkorder)"
verbose_name_plural = "Workorders (class: AssetWorkorder)"
def __str__(self):
return self.name
class AssetChecklist(MaxenBaseModel):
asset = models.ForeignKey("Asset", null=False,
on_delete=models.CASCADE)
checklist = models.ForeignKey("Checklist", null=False,
on_delete=models.CASCADE)
verbose_name = "AssetChecklist"
verbose_name_plural = "AssetChecklists"
def __str__(self):
return self.asset.name + " <-> " + self.checklist.name
class Asset(MaxenBaseModel):
"""
Assets can be just about anything, from buildings to machines or individual
components of a system such as sensors or controllers. Assets are an abstract
concept enabling hierarchical organization pf elements by relating them through
parent/child relationships or semantic relationships.
NOTE:
Assets having both an AssetCategory and an AssetType fk AND an AssetType having
its own AssetCategory fk is a bit funky but the OG model was built that way as
well.
"""
name = models.CharField("Asset name", max_length=255)
description = models.CharField(
"Description", max_length=512, blank=True, null=True)
organizationsite = models.ForeignKey(
"OrganizationSite",
verbose_name="Organization site",
on_delete=models.CASCADE,
)
thumbnail2 = models.ImageField(
"thumbnail2", storage=ThumbnailStorage, blank=True, null=True
)
# FIXME: Remove once the migration to thumbnail2 is done
thumbnail = models.ImageField(
"thumbnail",
upload_to="s3temp",
max_length=1024,
blank=True,
null=True,
help_text="Thumbnail image for the asset",
)
assetcategory = models.ForeignKey(
"AssetCategory",
verbose_name="Asset category",
blank=True,
null=True,
on_delete=models.CASCADE,
)
assettype = ChainedForeignKey(
AssetType,
chained_field="assetcategory",
chained_model_field="assetcategory",
show_all=False,
auto_choose=True,
sort=True,
blank=True,
null=True,
verbose_name="Asset type",
help_text="Linked type",
on_delete=models.CASCADE,
)
organizationcontacts = models.ManyToManyField(
"OrganizationContact",
verbose_name="Organization category",
blank=True,
)
graph = models.JSONField(
"Graph",
blank=True,
null=True,
help_text="Graph representation cache, update on save of an assetrelationship "
"or an asset",
)
class Meta:
ordering = ["id"]
verbose_name = "Asset"
verbose_name_plural = "Assets"
def __str__(self):
return self.name
#classmethod
def get_visible_for_user(cls, user: User) -> QuerySet:
exposed_organizationsites = OrganizationSite.get_visible_for_user(user)
return cls.objects.filter(organizationsite__in=exposed_organizationsites)
def update_graph(self):
G = nx.Graph()
nodes = Asset.objects.filter(organizationsite=self.organizationsite)
for n in nodes:
G.add_node(n.id)
edges = AssetRelationship.objects.filter(
Q(assetA__organizationsite=self.organizationsite)
| Q(assetB__organizationsite=self.organizationsite)
)
for e in edges:
G.add_edge(e.assetA.id, e.assetB.id)
assets_connected_to = node_connected_component(G, self.id)
# build vizjs json graph
nodes = []
edges = []
for a in assets_connected_to:
asset = Asset.objects.filter(id=a).get()
node = {
"id": asset.id,
"label": asset.name,
"group": "",
"meta": {
"asset_displayname": asset.name,
"asset_type": asset.assettype.name
if asset.assettype is not None
else "",
"asset_category": asset.assetcategory.name
if asset.assetcategory is not None
else "",
},
}
nodes.append(node)
relationships = AssetRelationship.objects.filter(
Q(assetA__in=list(assets_connected_to))
| Q(assetB__in=list(assets_connected_to))
).distinct()
for r in relationships:
edge = {
"from": r.assetA.id,
"to": r.assetB.id,
"label": r.relationshiptype.name,
"meta": {
"link_id": r.id,
"link_name": r.assetA.name
+ " "
+ r.relationshiptype.name
+ " "
+ r.assetB.name,
"link_type": r.relationshiptype.name,
},
}
edges.append(edge)
graph = {"nodes": nodes, "edges": edges}
self.graph = graph
class Checklist(MaxenBaseModel):
organization = models.ForeignKey(
"Organization", verbose_name="Organization", null=True, on_delete=models.CASCADE
)
name = models.CharField("Checklist Name", max_length=255)
description = models.CharField(
"Description", max_length=255, blank=True, null=True)
class Meta:
ordering = ["id"]
verbose_name = "Checklist"
verbose_name_plural = "Checklists"
constraints = [
models.UniqueConstraint(
fields=['organization', 'name'], name='unique_organization_name_combination'
)
]
def __str__(self):
return self.name
class MaxenBaseModel(models.Model):
"""
Base model with timestamps
"""
created = models.DateTimeField(
"Created on",
auto_now_add=True,
editable=False,
help_text="This field is autopopulated by the platform and represents the"
" creation timestamp of the object",
)
updated = models.DateTimeField(
"Updated on",
auto_now=True,
editable=False,
help_text="This field is autopopulated by the platform and represents the"
" object's last update's timestamp",
)
class Meta:
abstract = True # add this to base models so it doesn't get migrated
I'm creating a music rating app and I'm making a serializer for albums which has many relations and one aggregation method serializer which I think is causing all the trouble. The method gets average and count of reviews for every album. Is there any way I can decrease the number of queries for more performance?
All my models
class Artist(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255)
image = models.FileField(null=True, blank=True,
upload_to=rename_artist_image)
background_image = models.FileField(
null=True, blank=True, upload_to=rename_artist_bg_image)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self) -> str:
return self.name
class Album(models.Model):
RELEASE_TYPE_ALBUM_CHOICES = [
("LP", "LP"),
("EP", "EP"),
("Single", "Single"),
("Live", "Live"),
]
title = models.CharField(max_length=255)
slug = models.SlugField(max_length=255)
release_date = models.DateField(null=True)
artist_id = models.ForeignKey(
Artist, on_delete=models.PROTECT, related_name="albums"
)
art_cover = models.FileField(
null=True, blank=True, upload_to=rename_album_art_cover)
release_type = models.CharField(max_length=10,
choices=RELEASE_TYPE_ALBUM_CHOICES, default="LP")
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self) -> str:
return self.title
class Genre(models.Model):
name = models.CharField(max_length=255)
def __str__(self) -> str:
return self.name
class AlbumGenre(models.Model):
album_id = models.ForeignKey(
Album, on_delete=models.PROTECT, related_name="album_genres")
genre_id = models.ForeignKey(
Genre, on_delete=models.PROTECT, related_name="album_genres")
def __str__(self) -> str:
return self.genre_id.name
class AlbumLink(models.Model):
SERVICE_NAME_CHOICES = [
("spotify", "Spotify"),
("tidal", "Tidal"),
("amazonMusic", "Amazon Music"),
("appleMusic", "Apple Music"),
]
service_name = models.CharField(
max_length=15, choices=SERVICE_NAME_CHOICES)
url = models.CharField(max_length=255)
album_id = models.ForeignKey(
Album, on_delete=models.PROTECT, related_name="album_links")
def __str__(self) -> str:
return f"{self.service_name} - {self.url}"
class Track(models.Model):
title = models.CharField(max_length=255)
position = models.PositiveIntegerField()
album_id = models.ForeignKey(
Album, on_delete=models.PROTECT, related_name="tracks")
duration = models.DurationField(null=True)
def __str__(self) -> str:
return f"{self.position}. {self.title} - {self.duration}"
class AlbumOfTheYear(models.Model):
album_id = models.OneToOneField(
Album, on_delete=models.PROTECT, related_name="aoty")
position = models.IntegerField()
def __str__(self) -> str:
return str(self.position)
class Review(models.Model):
reviewer_id = models.ForeignKey(Reviewer, on_delete=models.PROTECT)
rating = models.IntegerField(
validators=[MaxValueValidator(100), MinValueValidator(0)]
)
review_text = models.TextField(null=True)
album_id = models.ForeignKey(
Album, on_delete=models.PROTECT, related_name="reviews")
created_at = models.DateTimeField(auto_now_add=True)
That's how album serializer looks.
"id": 2,
"title": "OK Computer",
"slug": "ok-computer",
"created_at": "2022-02-22T21:51:52.528148Z",
"artist": {
"id": 13,
"name": "Radiohead",
"slug": "radiohead",
"image": "http://127.0.0.1:8000/media/artist/images/radiohead.jpg",
"background_image": "http://127.0.0.1:8000/media/artist/bg_images/radiohead.jpg",
"created_at": "2022-02-22T00:00:00Z"
},
"art_cover": "http://127.0.0.1:8000/media/album/art_covers/ok-computer_cd5Vv6U.jpg",
"genres": [
"Alternative Rock",
"Art Rock"
],
"reviews": {
"overall_score": null,
"number_of_ratings": 0
},
"release_date": "1997-05-28",
"release_type": "LP",
"tracks": [
{
"position": 1,
"title": "Airbag",
"duration": "00:04:47"
},
{
"position": 2,
"title": "Paranoid Android",
"duration": "00:06:27"
}
],
"links": [
{
"service_name": "spotify",
"url": "https://open.spotify.com/album/6dVIqQ8qmQ5GBnJ9shOYGE?si=L_VNH3HeSMmGBqfiqKiGWA"
}
],
"aoty": null
Album serializer with method that gets average and count of reviews of album
class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackSerializer(many=True, read_only=True)
genres = StringRelatedField(
source="album_genres", many=True, read_only=True)
aoty = StringRelatedField(read_only=True)
links = AlbumLinkSerializer(
source="album_links", many=True, read_only=True)
artist = SimpleArtistSerializer(source="artist_id")
def get_avg_and_count_of_reviews(self, album: Album):
reviews = Review.objects.only("rating").filter(album_id=album.id).aggregate(
overall_score=Avg(F("rating"), output_field=IntegerField()), number_of_ratings=Count(F("rating"), output_field=IntegerField()))
return reviews
reviews = serializers.SerializerMethodField(
method_name="get_avg_and_count_of_reviews")
class Meta:
model = Album
fields = ["id",
"title",
"slug",
"created_at",
"artist",
"art_cover",
"genres",
"reviews",
"release_date",
"release_type",
"tracks",
"links",
"aoty"]
# Save slug
def create(self, validated_data):
slug = slugify(validated_data["title"])
return Album.objects.create(slug=slug, **validated_data)
Here is a queryset in album Viewset
class AlbumViewSet(ModelViewSet):
queryset = Album.objects.prefetch_related("tracks").prefetch_related("album_genres").prefetch_related(
"album_links").prefetch_related("reviews").select_related("aoty").select_related("artist_id").all()
First you need to change your aggregate that you call once for every Album to an annotation, this will remove all of those extra aggregation queries
class AlbumViewSet(ModelViewSet):
queryset = Album.objects.prefetch_related(
"tracks",
"album_genres",
"album_links",
"reviews"
).select_related(
"aoty",
"artist_id"
).annotate(
overall_score=Avg(F("reviews__rating"), output_field=IntegerField()),
number_of_ratings=Count(F("reviews__rating"), output_field=IntegerField())
)
Now you can replace your reviews field with two regular IntegerFields
class AlbumSerializer(serializers.ModelSerializer):
...
overall_score = serializers.IntegerField(source="overall_score")
number_of_ratings = serializers.IntegerField(source="number_of_ratings")
so I upgraded from django 3.1 to 3.2 and on two of my models when I make migrations it keeps forcing me to change the auto id to BigAutoField even though I have (and had) DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' in my settings file before I updated.
operations = [
migrations.AlterField(
model_name='device',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
)
What is strange is that it is only affecting a couple models, but the rest are fine and they all use AutoField as well.
I am not against using BigAutoField but the migration fails because of foreignkey constraints.
I have deleted the migrations in question and also scrubbed them from the applied migrations table in the database. How can I stop Django from forcing this migration? I am at a loss right now.
Here is my Device Model. As you can see I have not specifically set the primary key, which I have not done on any other model either and those are fine.
from django.db import models
from company.models import Company
from django.db.models.signals import pre_save, post_save
from main.models import uuid_pre_save_generator
from django.conf import settings
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
import json
from django.urls import reverse
from django.utils.html import escape
from django.core.validators import RegexValidator, FileExtensionValidator
class UploadedImage(models.Model):
uuid = models.CharField(max_length=7, blank=True, unique=True, verbose_name='Image ID')
image = models.ImageField(null=True, blank=True, upload_to="images/",
validators=[FileExtensionValidator(['jpg', 'png', 'jpeg'], 'Only .jpg files allowed')])
objects = models.Manager()
class Meta:
verbose_name = "Uploaded Image"
verbose_name_plural = "Uploaded Images"
def __str__(self):
return self.uuid
pre_save.connect(uuid_pre_save_generator, sender=UploadedImage)
def update_device_theme(sender, instance, *args, **kwargs):
related_devices = instance.devices.all()
if related_devices:
for d in related_devices:
d.save()
def alert_device_update(sender, instance, *args, **kwargs):
device = Device.objects.get(uuid=instance.uuid)
channel_layer = get_channel_layer()
group_name = 'connect_{}'.format(instance.uuid)
data = json.dumps({
'message': {
'type': 'device_update',
'text': '',
'data': {
'device': device.connect,
},
'sender': {
'type': 'server',
'uuid': '',
'first_name': '',
'last_name': '',
'company': '',
'initial': '',
'display_name': 'Server',
},
},
})
async_to_sync(channel_layer.group_send)(
group_name,
{
'type': 'chatroom.message',
'text': data
}
)
class DeviceDisplayTheme(models.Model):
uuid = models.CharField(max_length=7, blank=True, unique=True, verbose_name='Theme ID')
name = models.CharField(max_length=30, blank=False, null=False, verbose_name='Theme Name')
show_header = models.BooleanField(default=True, verbose_name='Show Header')
show_footer = models.BooleanField(default=True, verbose_name='Show Footer')
header_bg_color = models.CharField(max_length=30, blank=False, null=False,
verbose_name='Header Background Color',
default='rgba(255,255,255,.1)',
validators=[
RegexValidator(
regex=r"^^rgba[(](?:\s*0*(?:\d\d?(?:\.\d+)?(?:\s*%)?|"
r"\.\d+\s*%|100(?:\.0*)?\s*%|(?:1\d\d|2[0-4]\d|"
r"25[0-5])(?:\.\d+)?)\s*,){3}\s*0*(?:\.\d+|1(?:\.0*)?)\s*[)]$",
message='The header background color you have chosen is not formatted'
' correctly',
),
])
badge_bg_color = models.CharField(max_length=30, blank=False, null=False,
verbose_name='Device ID Badge Background Color',
default='rgba(255,255,255,.2)',
validators=[
RegexValidator(
regex=r"^^rgba[(](?:\s*0*(?:\d\d?(?:\.\d+)?(?:\s*%)?|"
r"\.\d+\s*%|100(?:\.0*)?\s*%|(?:1\d\d|2[0-4]\d|"
r"25[0-5])(?:\.\d+)?)\s*,){3}\s*0*(?:\.\d+|1(?:\.0*)?)\s*[)]$",
message='The badge background color you have chosen is not formatted'
' correctly',
),
])
bg_color = models.CharField(max_length=30, blank=False, null=False,
verbose_name='Background Color',
default='rgba(35,35,35,1)',
validators=[
RegexValidator(
regex=r"^^rgba[(](?:\s*0*(?:\d\d?(?:\.\d+)?(?:\s*%)?|"
r"\.\d+\s*%|100(?:\.0*)?\s*%|(?:1\d\d|2[0-4]\d|"
r"25[0-5])(?:\.\d+)?)\s*,){3}\s*0*(?:\.\d+|1(?:\.0*)?)\s*[)]$",
message='The background color you have chosen is not formatted correctly',
),
])
font_color = models.CharField(max_length=30, blank=False, null=False,
verbose_name='Font Color',
default='rgba(255,255,255,1)',
validators=[
RegexValidator(
regex=r"^^rgba[(](?:\s*0*(?:\d\d?(?:\.\d+)?(?:\s*%)?|"
r"\.\d+\s*%|100(?:\.0*)?\s*%|(?:1\d\d|2[0-4]\d|"
r"25[0-5])(?:\.\d+)?)\s*,){3}\s*0*(?:\.\d+|1(?:\.0*)?)\s*[)]$",
message='The font color you have chosen is not formatted correctly',
),
])
idle_text = models.CharField(max_length=50, blank=True, null=True,
verbose_name="Idle Text",
default='Press Help Button In Case of Emergency')
help_requested_text = models.CharField(max_length=50, blank=True, null=True,
verbose_name="Help Requested Text",
default='Emergency Help Requested')
active_text = models.CharField(max_length=50, blank=True, null=True,
verbose_name="Active Call Text",
default='Emergency Call Accepted')
response_prompt_text = models.CharField(max_length=50, blank=True, null=True,
verbose_name="Response Prompt Text",
default='Press Yes/No Buttons to Respond')
company = models.ForeignKey(Company, related_name='themes', verbose_name='Company',
blank=True, null=True, on_delete=models.CASCADE)
date_updated = models.DateTimeField(auto_now=True)
source_date_updated = models.DateTimeField(blank=True, null=True)
source_uuid = models.CharField(max_length=7, blank=True, null=True, verbose_name='Source ID')
objects = models.Manager()
class Meta:
verbose_name = "Device Display Theme"
verbose_name_plural = "Device Display Themes"
def __str__(self):
return self.name
#property
def settings(self):
idle_text = '' if not self.idle_text else self.idle_text
help_requested_text = '' if not self.help_requested_text else self.help_requested_text
active_text = '' if not self.active_text else self.active_text
response_prompt_text = '' if not self.response_prompt_text else self.response_prompt_text
return {
'pk': self.pk,
'name': escape(self.name),
'show_header': self.show_header,
'show_footer': self.show_footer,
'header_bg_color': self.header_bg_color,
'bg_color': self.bg_color,
'badge_bg_color': self.badge_bg_color,
'font_color': self.font_color,
'idle_text': escape(idle_text),
'help_requested_text': escape(help_requested_text),
'active_text': escape(active_text),
'response_prompt_text': escape(response_prompt_text),
}
#property
def settings_json(self):
return json.dumps(self.settings)
pre_save.connect(uuid_pre_save_generator, sender=DeviceDisplayTheme)
post_save.connect(update_device_theme, sender=DeviceDisplayTheme)
class Device(models.Model):
UUID_CREATED = 0
PROGRAMMED = 1
ASSIGNED = 2
lifecycle_stages = [
(UUID_CREATED, 'Unique ID Created'),
(PROGRAMMED, 'Memory Card Programmed'),
(ASSIGNED, 'Owner Assigned'),
]
statuses = [
('idle', 'Idle'),
('requested', 'Incoming Call Requested'),
('active', 'Live Call'),
]
uuid = models.CharField(max_length=6, blank=True, unique=True, verbose_name='Device ID')
# Call Status [ Idle, Requested, Active ]
state = models.CharField(max_length=10, choices=statuses, blank=False, default='idle', verbose_name="Call Status")
active = models.BooleanField(default=False, verbose_name='Active')
self_monitored = models.BooleanField(default=False, verbose_name='Self Monitored')
# Display Theme
theme = models.ForeignKey(DeviceDisplayTheme, related_name='devices', verbose_name='Display Theme',
blank=False, null=False, default=1, on_delete=models.SET_DEFAULT)
# Programming & Assignment
initialized = models.BooleanField(default=False, verbose_name='Initialized')
lifecycle = models.IntegerField(choices=lifecycle_stages, default=0, verbose_name="Lifecycle Stage")
software_version = models.CharField(max_length=12, blank=True, null=True, verbose_name='Software Version')
model_number = models.CharField(max_length=12, blank=True, null=True, verbose_name='Model Number')
activation_code = models.CharField(max_length=5, blank=True, null=True, verbose_name='Activation Code')
# Relationships
owner = models.ForeignKey(Company, related_name='devices', verbose_name='Device Owner',
blank=True, null=True, on_delete=models.SET_NULL)
callcenter = models.ForeignKey(Company, related_name='monitored_devices', verbose_name='Call Center',
on_delete=models.CASCADE, blank=True, null=True)
# Location & Address Details
location = models.CharField(max_length=255, blank=True, verbose_name='Device Identifier')
address = models.CharField(max_length=255, verbose_name="Street Address", blank=True)
address2 = models.CharField(max_length=255, verbose_name="Apartment, Unit, Suite, or Floor", blank=True)
address_locality = models.CharField(max_length=255, verbose_name="City/Town", blank=True)
address_state = models.CharField(max_length=255, verbose_name="State", blank=True)
address_postcode = models.CharField(max_length=55, verbose_name="Zip Code", blank=True)
address_country = models.CharField(max_length=55, verbose_name="Country", blank=True)
# Call-in Phone Number
phone_number = models.CharField(max_length=14, blank=True, null=True, verbose_name="Call-in Phone Number",
validators=[
RegexValidator(
regex=r"^\(\d{3}\)\s\d{3}-\d{4}$",
message='Phone number format is not valid, try (000) 000-0000',
),
])
# Timestamps
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
date_last_online = models.DateTimeField(blank=True, null=True)
objects = models.Manager()
class Meta:
verbose_name = "Device"
verbose_name_plural = "Devices"
ordering = ['uuid']
def __str__(self):
return self.uuid
def set_idle_state(self):
self.state = 'idle'
def set_requested_state(self):
self.state = 'requested'
def set_active_state(self):
self.state = 'active'
#property
def full_address(self):
def check_address_parts(value):
if value == '' or value == ',':
return False
else:
return True
address_parts = [
"%s," % self.address,
"%s," % self.address2,
"%s" % self.address_locality,
"%s," % self.address_state,
"%s" % self.address_postcode,
]
return ' '.join(filter(check_address_parts, address_parts))
#property
def location_and_full_address(self):
if self.location:
if self.full_address:
return '{} - {}'.format(self.full_address, self.location)
return self.location
return self.full_address
#property
def entry(self):
address = '<div class="fs-6">{}</div>'.format(escape(self.full_address)) if self.full_address else ''
location = '<div class="fs-6">{}</div>'.format(escape(self.location)) if self.location else ''
return ' '.join((address, location))
#property
def connect(self):
owner = '' if not self.owner else self.owner.connect
callcenter = '' if not self.callcenter else self.callcenter.connect
return {
'type': 'device',
'pk': self.pk,
'uuid': self.uuid,
'state': self.state,
'first_name': '',
'last_name': '',
'owner': owner,
'callcenter': callcenter,
'initial': 'D',
'display_name': 'Device {}'.format(self.uuid),
'location': escape(self.location),
'address': escape(self.full_address),
'connect_version': settings.CONNECT_VERSION,
'url_live_call': reverse('device_live_call', args=[self.uuid]),
'theme': self.theme.settings,
}
#property
def connect_json(self):
return json.dumps(self.connect)
#property
def connect_version(self):
return settings.CONNECT_VERSION
pre_save.connect(uuid_pre_save_generator, sender=Device)
post_save.connect(alert_device_update, sender=Device)
class DeviceId(models.Model):
STAGED = 0
REQUESTED = 1
CAPTURED = 2
EXPIRED = 3
device_uuid_status = [
(STAGED, 'Staged'),
(REQUESTED, 'Requested'),
(CAPTURED, 'Captured'),
(EXPIRED, 'Expired'),
]
uuid = models.CharField(max_length=7, blank=True, unique=True, verbose_name='Device ID')
status = models.IntegerField(choices=device_uuid_status, default=0, verbose_name="ID Status")
programmer = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='programmer', null=True,
on_delete=models.PROTECT, verbose_name='Programmer')
# Timestamps
date_created = models.DateTimeField(auto_now_add=True)
date_requested = models.DateTimeField(blank=True, null=True)
date_captured = models.DateTimeField(blank=True, null=True)
date_expired = models.DateTimeField(blank=True, null=True)
objects = models.Manager()
class Meta:
verbose_name = "Device Id"
verbose_name_plural = "Device Ids"
ordering = ['date_created']
pre_save.connect(uuid_pre_save_generator, sender=DeviceId)
Well, I figured it out. For some reason the BigAutoField was set in the apps.py file in the app
from django.apps import AppConfig
class DeviceConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'device'
I'm using Django and Django Rest Framework to represent a 'BaseRecipe' model. This model has a M2M field to represent the ingredients of that recipe. That M2M field relates a 'Product' object with the 'BaseRecipe' object and extends that two models with another field to represent the 'quantity'. I'm trying to retrieve a list of those ingredients in the BaseRecipeSerializer, but only the id's are returned.
Any ideas why?
Thank you in advance!
My models.py:
class Product(models.Model):
name = models.CharField(_('Name'), max_length=255, help_text=_('Product name'))
supplier = models.ForeignKey(Supplier, blank=True, related_name='supplier_products', on_delete=models.CASCADE)
serial_no = models.CharField(_('Serial number'), max_length=50, blank=True,
help_text=_('Product serial number. Max 50 characters.'))
allergens = models.ManyToManyField(Allergen, blank=True, related_name='product_allergens')
description = models.TextField(_('Description'), blank=True, help_text=_('Additional product information.'))
is_vegan = models.BooleanField(_('Vegan'), default=False)
is_halal = models.BooleanField(_('Halal'), default=False)
format_container = models.CharField(_('Format container'), max_length=6, choices=CONTAINERS, blank=True,
help_text=_('Package format'))
format_quantity = models.DecimalField(_('Format quantity'), blank=True, null=True, max_digits=9, decimal_places=3,
help_text=_('Format quantity sell'))
format_unit = models.CharField(_('Format unit'), max_length=6, choices=UNITS, blank=True)
quantity = models.DecimalField(_('Quantity'), blank=True, null=True, max_digits=9, decimal_places=3,
help_text=_('Quantity per unit provided'))
type = models.CharField(_('Type'), max_length=255, help_text=_('Type'), blank=True)
unit = models.CharField(_('Unit'), max_length=6, choices=UNITS)
unit_price = models.DecimalField(_('Unit price'), blank=True, null=True, max_digits=9, decimal_places=3)
class Meta:
ordering = ['name', ]
#property
def price(self) -> Decimal:
return self.quantity * self.unit_price
def __str__(self):
return self.name
class BaseRecipe(models.Model):
user = models.ForeignKey(User, null=True, on_delete=models.CASCADE, related_name='user_base_recipes')
restaurant = models.ForeignKey(Restaurant, null=True, on_delete=models.SET_NULL,
related_name='restaurant_base_recipes')
title = models.CharField(_('Base recipe title'), max_length=255, help_text=_('Base recipe. Example: Chicken broth'),
blank=True)
elaboration = models.TextField(_('Elaboration'), blank=True, help_text=_('Base recipe making instructions.'))
quantity = models.DecimalField(_('Quantity'), max_digits=9, decimal_places=3,
help_text=_('Quantity produced with this recipe (after cooking). '))
unit = models.CharField(_('Unit'), max_length=6, choices=UNITS)
expiry_date = models.DateField(_('Expiry date'), help_text=_('Last day product is safe to consume.'))
ingredients = models.ManyToManyField(Product, through='IngredientBaseRecipe', related_name='base_recipe_ingredients')
image = models.ImageField(_('Picture'), upload_to=get_picture_path, blank=True, null=True)
exhausted = models.BooleanField(_('Exhausted'), default=False, help_text=_('If it\'s needed to produce more.'))
next_batch_date = models.DateField(_('Next batch date'), blank=True,
help_text=_('When it\'s necessary to prepare more of this base recipe.'))
storage = models.CharField(_('Storage'), max_length=12, choices=STORAGE_METHODS, blank=True)
class Meta:
ordering = ['-id']
#property
def cost(self) -> Decimal:
return sum([ingredient.cost for ingredient in self.ingredients.all()])
#property
def allergens(self):
allergens_ids = IngredientBaseRecipe.objects.filter(base_recipe=self).values_list('product__allergens',
flat=True)
allergens = Allergen.objects.filter(id__in=allergens_ids)
return allergens
def __str__(self):
return self.title
class IngredientBaseRecipe(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
base_recipe = models.ForeignKey(BaseRecipe, on_delete=models.CASCADE)
product_quantity = models.DecimalField(_('Product quantity'), max_digits=9, decimal_places=3, default=0.0)
class Meta:
ordering = ['-id']
#property
def allergens(self):
return self.product.allergens.all()
#property
def cost(self) -> Decimal:
return self.quantity * self.product.unit_price
def __str__(self):
return self.product.name
serializer.py:
class SimpleProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ['id', 'name', ]
class IngredientBaseRecipeSerializer(serializers.ModelSerializer):
# product_name = serializers.ReadOnlyField(source='product.name')
product = SimpleProductSerializer(read_only=True)
class Meta:
model = IngredientBaseRecipe
exclude = ['base_recipe', ]
class BaseRecipeSerializer(serializers.ModelSerializer):
ingredients = IngredientBaseRecipeSerializer(many=True, read_only=True)
allergens = AllergenSerializer(many=True, read_only=True)
cost = serializers.FloatField(read_only=True)
class Meta:
model = BaseRecipe
fields = '__all__'
Result:
{
"count": 3,
"next": null,
"previous": null,
"results": [
{
"id": 5,
"ingredients": [
{
"id": 1
},
{
"id": 3
},
{
"id": 3
}
],
"allergens": [
{
"id": 9,
"name": "Celery",
"icon": null
},
{
"id": 12,
"name": "Sulphites",
"icon": null
}
],
"title": "Tipical Spanish",
"elaboration": "No se tio.",
"quantity": "23232.000",
"unit": "l",
"expiry_date": "2021-02-28",
"image": null,
"exhausted": false,
"next_batch_date": "2021-03-01",
"storage": "freezer",
"user": 1,
"restaurant": 1
},
{
"id": 3,
"ingredients": [],
"allergens": [],
"cost": 0.0,
"title": "Chicken broth",
"elaboration": "One, two, three.",
"quantity": "45.000",
"unit": "l",
"expiry_date": "2021-02-28",
"image": null,
"exhausted": false,
"next_batch_date": "2021-03-01",
"storage": "freezer",
"user": 1,
"restaurant": 1
}
]
}
BaseRecipe.ingredients will return a queryset of Product instances but you are passing them to the IngredientBaseRecipeSerializer. You need to change the source for this field so that you pass the related IngredientBaseRecipe instances to this field
class BaseRecipeSerializer(serializers.ModelSerializer):
ingredients = IngredientBaseRecipeSerializer(
many=True,
read_only=True,
source='ingredientbaserecipe_set'
)
...
I have a Course model with Option as ClusterableModel and Staff. Staff is related to Course model and Staf can choose the Option. I've done it similar things before with normal django, but I can't seems to figure out how to do it with Wagtail. The Option is showing normally on Course's Page but its empty on Staff's Tab. Here is my model :
class Option(models.Model):
name = models.CharField(max_length=255)
desc = models.CharField(max_length=255, blank=True, null=True)
class Meta:
abstract = True
def __str__(self):
return self.name
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
self.tajuk = self.name.title()
content_panels = [
FieldPanel('name'),
FieldPanel('desc')
]
class OptionPage(Orderable, Option):
page = ParentalKey("Course", related_name='course_option')
class Staf(Orderable):
page = ParentalKey('course.Course', related_name='pcourse')
jxr = models.ForeignKey('jxr.Staf',
null=True,
blank=True,
on_delete=models.PROTECT,
related_name='+'
)
vegetarian = models.BooleanField(default=False)
attend = models.BooleanField(default=False)
reason = models.CharField(max_length=255, blank=True, null=True)
replacement = models.ForeignKey('jxr.Staf',
null=True,
blank=True,
on_delete=models.PROTECT,
related_name='+'
)
option = models.ForeignKey(OptionPage,blank=True, null=True)
class Meta:
verbose_name_plural = 'Staf'
panels = [
FieldPanel('jxr'),
FieldPanel('vegetarian'),
FieldPanel('option'),
FieldPanel('attend'),
FieldPanel('reason'),
FieldPanel('replacement'),
]
class Course(Page):
tags = ClusterTaggableManager(through=CourseTag, blank=True)
picture = models.ForeignKey(
'wagtailimages.Image',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+'
)
State = models.ForeignKey(State, default=1,
related_name='+',
on_delete=models.PROTECT,
)
location = RichTextField()
start = models.DateTimeField("Start",default=timezone.now)
end = models.DateTimeField("End", default=timezone.now)
max_staf = models.PositiveIntegerField(default=1)
intro = StreamField(CourseBlock())
status = models.CharField(max_length=30, choices=STATUS_KURSUS)
search_fields = Page.search_fields + [
index.SearchField('title'),
index.SearchField('intro')]
parent_page_types =['course.CourseIndex']
subpage_types=[]
content_panels = [
FieldPanel('title', classname="full title"),
MultiFieldPanel([
InlinePanel('kat_kursus', label='Category'),
FieldPanel('tags'),
InlinePanel('course_option', label='Options')
]),
ImageChooserPanel('picture'),
FieldPanel('state'),
FieldPanel('location'),
FieldPanel('start'),
FieldPanel('end'),
FieldPanel('max_staf'),
StreamFieldPanel('intro'),
FieldPanel('status'),
]
staf_panel = [
InlinePanel('pkursus', label='Staf')
]
edit_handler = TabbedInterface([
ObjectList(content_panels, heading='Content'),
ObjectList(staf_panel, heading='Staf'),
ObjectList(Page.promote_panels, heading='Promote'),
ObjectList(Page.settings_panels, heading='Settings', classname="settings"),
])