53e31d81d03e8d1bb95ac759ba5af392241e29f5
Django/Django Model Field Tricks.md
| ... | ... | @@ -0,0 +1,183 @@ |
| 1 | +# 10 Django Model Field Tricks You Probably Didn’t Know About | by Haider Alaamery | Dec, 2024 | Medium |
|
| 2 | + |
|
| 3 | +https://halaamery.medium.com/10-django-model-field-tricks-you-probably-didnt-know-about-fc97f3148bc0 |
|
| 4 | + |
|
| 5 | + |
|
| 6 | +[Haider Alaamery](https://halaamery.medium.com/) |
|
| 7 | + |
|
| 8 | +1\. Customizing Field Validation with `clean()` |
|
| 9 | +----------------------------------------------- |
|
| 10 | + |
|
| 11 | +Did you know you can add custom validation logic to specific fields using the `clean()` method? |
|
| 12 | + |
|
| 13 | +``` |
|
| 14 | +from django.db import models |
|
| 15 | +from django.core.exceptions import ValidationError |
|
| 16 | +class Product(models.Model): |
|
| 17 | + name = models.CharField(max_length=100) |
|
| 18 | + price = models.DecimalField(max_digits=10, decimal_places=2) |
|
| 19 | +def clean(self): |
|
| 20 | + if self.price <= 0: |
|
| 21 | + raise ValidationError("Price must be greater than zero.") |
|
| 22 | +``` |
|
| 23 | + |
|
| 24 | + |
|
| 25 | +> Use `clean()` in your save logic or forms to ensure that invalid data never sneaks into your database. |
|
| 26 | + |
|
| 27 | +2\. Conditional Default Values |
|
| 28 | +------------------------------ |
|
| 29 | + |
|
| 30 | +Sometimes, you need default values to change dynamically based on some condition. |
|
| 31 | + |
|
| 32 | +``` |
|
| 33 | +from django.utils.timezone import now |
|
| 34 | +class Event(models.Model): |
|
| 35 | + name = models.CharField(max_length=100) |
|
| 36 | + start_date = models.DateTimeField(default=now) |
|
| 37 | + end_date = models.DateTimeField(default=lambda: now() + timedelta(days=7)) |
|
| 38 | +``` |
|
| 39 | + |
|
| 40 | + |
|
| 41 | +> This is great for timestamps or automatically generating default future dates. |
|
| 42 | + |
|
| 43 | +3\. **Unique Constraints for Multi-Field Uniqueness** |
|
| 44 | +----------------------------------------------------- |
|
| 45 | + |
|
| 46 | +Replace the deprecated unique\_together with UniqueConstraint for better control. |
|
| 47 | + |
|
| 48 | +``` |
|
| 49 | +from django.db import models |
|
| 50 | +class Membership(models.Model): |
|
| 51 | + user = models.ForeignKey('auth.User', on_delete=models.CASCADE) |
|
| 52 | + group = models.ForeignKey('Group', on_delete=models.CASCADE) |
|
| 53 | + class Meta: |
|
| 54 | + constraints = [ |
|
| 55 | + models.UniqueConstraint(fields=['user', 'group'], name='unique_membership') |
|
| 56 | + ] |
|
| 57 | +``` |
|
| 58 | + |
|
| 59 | + |
|
| 60 | +> This ensures no user can be added to the same group twice. |
|
| 61 | + |
|
| 62 | +4\. **Using** `**choices**` **with Enums** |
|
| 63 | +------------------------------------------ |
|
| 64 | + |
|
| 65 | +Django’s `choices` make fields cleaner, especially with Enums. |
|
| 66 | + |
|
| 67 | +``` |
|
| 68 | +from django.db import models |
|
| 69 | +class Status(models.TextChoices): |
|
| 70 | + PENDING = 'P', 'Pending' |
|
| 71 | + APPROVED = 'A', 'Approved' |
|
| 72 | + REJECTED = 'R', 'Rejected' |
|
| 73 | +class Request(models.Model): |
|
| 74 | + status = models.CharField(max_length=1, choices=Status.choices, default=Status.PENDING) |
|
| 75 | +``` |
|
| 76 | + |
|
| 77 | + |
|
| 78 | +> Access your choices like `Status.PENDING` instead of raw strings. |
|
| 79 | + |
|
| 80 | +5\. `through` for Many-to-Many Customization |
|
| 81 | +-------------------------------------------- |
|
| 82 | + |
|
| 83 | +Need extra data in your Many-to-Many relationships? Use `through`. |
|
| 84 | + |
|
| 85 | +``` |
|
| 86 | +class Author(models.Model): |
|
| 87 | + name = models.CharField(max_length=100) |
|
| 88 | +class Book(models.Model): |
|
| 89 | + title = models.CharField(max_length=100) |
|
| 90 | + authors = models.ManyToManyField(Author, through='Authorship') |
|
| 91 | +class Authorship(models.Model): |
|
| 92 | + author = models.ForeignKey(Author, on_delete=models.CASCADE) |
|
| 93 | + book = models.ForeignKey(Book, on_delete=models.CASCADE) |
|
| 94 | + role = models.CharField(max_length=50) # e.g., "Co-Author", "Editor" |
|
| 95 | +``` |
|
| 96 | + |
|
| 97 | + |
|
| 98 | +> This allows you to track additional details about the relationship. |
|
| 99 | + |
|
| 100 | +6\. Field-Level Permissions with `editable=False` |
|
| 101 | +------------------------------------------------- |
|
| 102 | + |
|
| 103 | +Prevent direct editing in admin while still allowing updates programmatically. |
|
| 104 | + |
|
| 105 | +``` |
|
| 106 | +class Order(models.Model): |
|
| 107 | + total_price = models.DecimalField(max_digits=10, decimal_places=2, editable=False) |
|
| 108 | +``` |
|
| 109 | + |
|
| 110 | + |
|
| 111 | +> You can still update `total_price` in your code but not in the admin. |
|
| 112 | + |
|
| 113 | +7\. Custom File Upload Paths |
|
| 114 | +---------------------------- |
|
| 115 | + |
|
| 116 | +Organize uploaded files dynamically with `upload_to`. |
|
| 117 | + |
|
| 118 | +``` |
|
| 119 | +def upload_to(instance, filename): |
|
| 120 | + return f"uploads/{instance.user.id}/{filename}" |
|
| 121 | +class Profile(models.Model): |
|
| 122 | + user = models.OneToOneField('auth.User', on_delete=models.CASCADE) |
|
| 123 | + avatar = models.ImageField(upload_to=upload_to) |
|
| 124 | +``` |
|
| 125 | + |
|
| 126 | + |
|
| 127 | +> Uploaded files are neatly stored based on the user’s ID. |
|
| 128 | + |
|
| 129 | +8\. Using `Property` Fields in Models |
|
| 130 | +------------------------------------- |
|
| 131 | + |
|
| 132 | +Django models can include computed properties. |
|
| 133 | + |
|
| 134 | +``` |
|
| 135 | +class Employee(models.Model): |
|
| 136 | + first_name = models.CharField(max_length=50) |
|
| 137 | + last_name = models.CharField(max_length=50) |
|
| 138 | + @property |
|
| 139 | + def full_name(self): |
|
| 140 | + return f"{self.first_name} {self.last_name}" |
|
| 141 | +``` |
|
| 142 | + |
|
| 143 | + |
|
| 144 | +> Access it like a field: `employee.full_name`. |
|
| 145 | + |
|
| 146 | +9\. Auto-Setting Fields with `save()` |
|
| 147 | +------------------------------------- |
|
| 148 | + |
|
| 149 | +Customize field behavior by overriding `save()`. |
|
| 150 | + |
|
| 151 | +``` |
|
| 152 | +class Article(models.Model): |
|
| 153 | + title = models.CharField(max_length=100) |
|
| 154 | + slug = models.SlugField(unique=True, blank=True) |
|
| 155 | + def save(self, *args, **kwargs): |
|
| 156 | + if not self.slug: |
|
| 157 | + self.slug = self.title.lower().replace(' ', '-') |
|
| 158 | + super().save(*args, **kwargs) |
|
| 159 | +``` |
|
| 160 | + |
|
| 161 | + |
|
| 162 | +> Automatically generate slugs from titles if not provided. |
|
| 163 | + |
|
| 164 | +10\. Soft Deletes with `is_archived` |
|
| 165 | +------------------------------------ |
|
| 166 | + |
|
| 167 | +Instead of deleting records, archive them. |
|
| 168 | + |
|
| 169 | +``` |
|
| 170 | +class BaseModel(models.Model): |
|
| 171 | + is_archived = models.BooleanField(default=False) |
|
| 172 | + def delete(self, *args, **kwargs): |
|
| 173 | + self.is_archived = True |
|
| 174 | + self.save() |
|
| 175 | + class Meta: |
|
| 176 | + abstract = True |
|
| 177 | +``` |
|
| 178 | + |
|
| 179 | + |
|
| 180 | +> Your data stays safe while appearing “deleted” to the user. |
|
| 181 | + |
|
| 182 | +**Conclusion:** |
|
| 183 | +Django’s model system is powerful, and these tricks can help you write cleaner, more efficient code. Which of these tricks are you already using? What’s your favorite? |
|
| ... | ... | \ No newline at end of file |