SeekingAd is a Django model for RiffMates classified advertisements: a musician can seek a band, or a band can seek a musician. It stores the ad's owner, date, direction, related musician or band, and written content. section 7.2.1
The model separates the advertisement's direction from the objects it connects:
- Use a CharField with TextChoices: "M" represents a musician and "B" represents a band. section 7.2.1
- Connect musician and band through nullable foreign keys, allowing the appropriate side of the advertisement to be populated. section 7.2.1
- Record the creation date automatically, associate an owner, store the ad in a TextField, and order records by date. section 7.2.1
Testing SeekingAd through Django Admin
Register SeekingAd with a ModelAdmin and display its id, date, owner, seeking label, and a shortened ad description. Run makemigrations and migrate, then add ads in the Admin while trying different seeking, musician, and band combinations to check validation. section 7.2.2
Listing 7.5 The SeekingAd model stores a RiffMates classified ad
# RiffMates/content/models.py
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.db import models
from bands.models import Musician, Band
class MusicianBandChoice(models.TextChoices): #1
MUSICIAN = "M"
BAND = "B"
class SeekingAd(models.Model):
date = models.DateField(auto_now_add=True) #2
owner = models.ForeignKey(User,
on_delete=models.CASCADE) #3
seeking = models.CharField(max_length=1,
choices=MusicianBandChoice.choices) #4
musician = models.ForeignKey(Musician, on_delete=models.SET_NULL,
blank=True, null=True) #5
band = models.ForeignKey(Band, on_delete=models.SET_NULL,
blank=True, null=True) #6
content = models.TextField() #7
class Meta:
ordering = ["date", ] #8
def __str__(self):
return f"SeekingAd(id={self.id}, seeking={self.seeking})"
def clean(self): #9
if self.seeking == MusicianBandChoice.MUSICIAN:
# Validate seeking musician case #10
if self.band is None:
raise ValidationError(
"Band field required when seeking a musician")
if self.musician is not None:
raise ValidationError(
("Musician field should be empty for a band "
"seeking a musician"))
else:
# Validate seeking band case #11
if self.musician is None:
raise ValidationError(
"Musician field required when seeking a band")
if self.band is not None:
raise ValidationError(
("Band field should be empty for a musician "
"seeking a band"))
super().clean()
Inspect the complete SeekingAd model definition, including TextChoices, relationships, ownership, content, and ordering. section 7.2.1
Listing 7.6 Register SeekingAdAdmin in the Django Admin
# RiffMates/content/admin.py
from django.contrib import admin
from django.utils.text import Truncator #1
from content.models import SeekingAd
@admin.register(SeekingAd) #2
class SeekingAdAdmin(admin.ModelAdmin):
list_display = ("id", "date", "owner", "seeking",
"show_ad", ) #3
def show_ad(self, obj):
return Truncator(obj.content).words(5,
truncate=' ...') #4
show_ad.short_description = "Ad" #5
Inspect the Django Admin registration that truncates long advertisement text for the summary listing. section 7.2.2
