Sobes.tech
Middle
262

What are thick models in Django?

Answer from AI

sobes.tech AI

"Fat Models" - this is an anti-pattern in Django, where a significant part of the application's business logic is implemented directly within model methods or in model managers.

Advantages of this approach (often used unconsciously):

  • Logic is close to the data it works with.
  • Easy access to model data within its methods.

Disadvantages of Fat Models:

  • Violation of the Single Responsibility Principle, models become overly complex.
  • Testing becomes more complicated: tests for models should cover both data logic and business logic.
  • Logic repetition: the same logic can be copied between different models or their methods.
  • Scalability and maintenance complexity: changing business logic affects models that should focus on data presentation.
  • Difficulty reusing logic in different parts of applications (e.g., in views and in management commands).

Alternatives and ways to avoid:

  • Services: Moving business logic into separate classes or functions that interact with models but are not part of their definition.
  • Managers: Using Django managers for database queries and possibly for some logic related to data selection, but not for complex business logic.
  • Forms: Using forms for validation and data processing before saving.
  • Task queues (e.g., Celery): For asynchronous or long-running business logic.

Example of a "fat model":

# models.py
from django.db import models

class Order(models.Model):
    user = models.ForeignKey('auth.User', on_delete=models.CASCADE)
    total_amount = models.DecimalField(max_digits=10, decimal_places=2)
    created_at = models.DateTimeField(auto_now_add=True)
    status = models.CharField(max_length=50, default='pending')

    # Business logic inside the model
    def process_payment(self, payment_method):
        if self.status != 'pending':
            return False # Order already processed

        # Complex payment processing logic (external API calls, discounts, etc.)
        success = self._integrate_payment_gateway(payment_method)

        if success:
            self.status = 'processed'
            self.save()
            self._send_confirmation_email()
            return True
        else:
            self.status = 'failed'
            self.save()
            return False

    def _integrate_payment_gateway(self, payment_method):
        # Simulation of integration
        print(f"Integrating with payment gateway using {payment_method} for order {self.id}")
        # Real API call logic could be here
        return True # Assuming success

    def _send_confirmation_email(self):
        # Email sending logic
        print(f"Sending confirmation email for order {self.id}")

In this example, the process_payment method contains logic that includes status check, integration with payment system, order status update, and email sending. This makes the Order model "fat". It is preferable to move this logic into a separate service.