PyConES 2016
Héctor Pablos López
Front-End developer @ StyleSage
www.stylesage.co
hector@stylesage.co
1. El problema: Modelos de Django "Gordos"
Fat model
Overweight model
1. El problema: Modelos de Django "Gordos"
Métodos que interaccionan con propiedades internas de otros modelos
class Car(models.Model):
owner = models.ForeignKey(User)
class User(models.Model):
# Fields and simple methods...
def get_car_models_owned(self)
return [car.model for car in self.car_set.all()]
1. El problema: Modelos de Django "Gordos"
Métodos que ejecutan queries
class User(models.Model):
# Fields and simple methods...
def get_red_owned_cars(self)
return self.car_set.filter(color='red')
1. El problema: Modelos de Django "Gordos"
Validaciones o tratamiento de excepciones
class User(models.Model):
# Fields and simple methods...
def get_owned_cars_built_in_year(self, year)
# Check the year is correct
try:
if 1900 <= year <= 2016:
return self.car_set.filter(build_year=year)
else:
raise SomeKindOfException
except Exception:
return "An error has happened"
1. El problema: Modelos de Django "Gordos"
1. El problema: Modelos de Django "Gordos"
Módulos controlador o de utilidades
# users_controller.py
def get_active_users():
return User.objects.filter(is_banned=False, account_expiry_date__gte=datetime.now())
# cars_controller.py
def get_luxury_cars()
return Car.objects.filter(price__get=100000)
Para tratar conjuntos de modelos
1. El problema: Modelos de Django "Gordos"
Vistas sobrecargadas que modifican los modelos
def buy_car(request, car_id):
try:
car_to_buy = Cars.objects.get(car_id)
if car_to_buy.price > request.user.available_money:
raise SomeKindOfException # Validating logic
request.user.car_set.add(car_to_buy) # Manipulating model
except Car.DoesNotExist:
raise SomeKindOfException()
1. El problema: Modelos de Django "Gordos"
Dependencias circulares
Model
"Controller"
View
1. El problema: Modelos de Django "Gordos"
Never write to a model field or call save() directly. Always use model methods and manager methods for state changing operations.
Tom Christie - https://www.dabapps.com/blog/django-models-and-encapsulation/
Healthy models, proxy models, model managers
2. La solución: Thin models, proxy models, model managers
class Car(models.Model):
owner = models.ForeignKey(User)
plate = models.CharField(max_length=8)
km_run = models.IntegerField()
@property
def mi_run(self):
return self.km_run * 0.621371
Nada más!!
2. La solución: Thin models, proxy models, model managers
class CarProxy(Car):
def add_to_km_run(self, km_to_add):
assert km_to_add > 0
self.km_run += km_to_add
self.owner.add_to_km_run(km_to_add)
self.save()
class Meta:
proxy = True
2. La solución: Thin models, proxy models, model managers
class CarManager:
def get_luxury_cars()
return Car.objects.filter(price__get=100000)
2. La solución: Thin models, proxy models, model managers
Las vistas sólo se encargan de:
def buy_car(request, car_id):
try:
car_to_buy = Cars.objects.get(car_id)
request.user.buy_car(car_to_buy)
return successful_response("Some message")
except Exception as e:
return map_exception_to_response(e)
2. La solución: Thin models, proxy models, model managers
Modelos
Proxies
Managers
Vistas
3. Los inconvenientes
Convertir un modelo a un proxy
car = User.objects.get(id=1)
car.__class__ = CarProxy
Convertir un queryset de modelos a queryset de proxies
user_owned_cars_qs = User.car_set
user_owned_cars_qs.model = CarProxy
Fácil de solucionar con un middleware
class ReplaceProxyUserInRequest(object):
"""
Middleware to process each request, changing the default request.user,
which is an instance of the User model, and replacing it with an
instance of UserProxy with the same data.
"""
def process_request(self, request):
if request.user.is_authenticated():
request.user.__class__ = UserProxy
return None
3. Los inconvenientes
Los proxies devuelven modelos, no otros proxies
class UserProxy(User):
class Meta:
proxy = True
@property
def workbook_set(self):
qs = super(UserProxy, self).car_set
qs.model = CarProxy
return qs
Los "sets" se pueden enmascarar con propiedades
3. Los inconvenientes
Los proxies devuelven modelos, no otros proxies
class CarProxy(User):
class Meta:
proxy = True
@property
def proxy_user(self):
owner = super(CarProxy, self).owner
owner.__class__ = CarProxy
return owner
3. Los inconvenientes
4. Las alternativas
Proyecto de Django para generar modelos polimórficos:
Se eliminan los drawbacks
https://github.com/django-polymorphic/django-polymorphic
4. Las alternativas
+ No afectan los inconvenientes mencionados
- Modelos de miles de líneas de código, con varios tipos de lógica mezclada
Respetando siempre buenas prácticas generales de programación:
Esta es sólo una forma de muchas de mejorar la estructura de código en Django (¡y funciona!)
Seguro que tienes la tuya, ¡Cuéntanosla!
Héctor Pablos López
Front-End developer @ StyleSage
www.stylesage.co
hector@stylesage.co
stylesage.co/careers