How to only create relevant model fields during a django test? - django

I am testing a method that requires me to create a fake record in my model. The model has over 40 fields. Is it possible to create a record with only the relevant model fields for the test so I don't have to populate the other fields? If so how would I apply it to this test case example.
models.py
class Contract():
company = models.CharField(max_length=255),
commission_rate = models.DecimalField(max_digits=100, decimal_places=2)
type = models.CharField(max_length=255)
offer = models.ForeignKey('Offer', on_delete=models.PROTECT)
notary = models.ForeignKey('Notary', on_delete=models.PROTECT)
jurisdiction = models.ForeignKey('Jurisdiction', on_delete=models.PROTECT)
active = models.BooleanField()
...
test.py
import pytest
from app.models import Contract
def calculate_commission(company, value):
contract = Contract.objects.get(company='Apple')
return value * contract.commission_rate
#pytest.mark.django_db
def test_calculate_commission():
#The only two model fields I need for the test
Contract.objects.create(company='Apple', commission_rate=0.2)
assert calculate_commission('Apple', 100) == 20

Try to use model_bakery to make an object record. Just populate fields you want and leave another blank, model_bakery will handle it. For the Detail, you can check this out model_bakery

Related

Put a constraint on an attribute of a related model

I have two models: Account and Transfer.
The model Account has the attribute currency (EUR, USD, JPY, etc.).
The model Transfer has two attributes account_from and account_to.
I want to add a constraint that checks that account_from uses the same currency as account_to.
I was thinking of adding such a constraint on Transfer model:
class Meta:
constraints = [
CheckConstraint(check=Q(account_from__currency=F('account_to__currency')), name='same_currency'),
]
But that doesn't work because of error
django.core.exceptions.FieldError: Joined field references are not permitted in this query
How do I do that ? Without relying on SQL. I know how to do that in SQL but I want to use the ORM. Or is it impossible to do that with Django ORM ?
Here are the two models (simplified to avoid noise):
class Tranfer(AuditedModel):
"""
The action of moving money from one account to another
"""
account_from = models.ForeignKey(Account, related_name="outgoing", on_delete=models.CASCADE)
account_to = models.ForeignKey(Account, related_name="incoming", on_delete=models.CASCADE)
class Account(AuditedModel):
"""
Recipient for money
"""
currency = models.CharField('currency', max_length=3, choices=(('EUR', 'Euro'), ('USD', 'American Dollars'), ('JPY', 'Japan Yen')))
class Meta:
constraints = [
CheckConstraint(check=Q(account_from__currency=F('account_to__currency')), name='same_currency'),
]
Follow the following steps for the solution of your problem.
The constrain section should be in the Transfer Model not in Account model.
Check constrain for any two relational model must be avoided.
The alternative for CheckConstraint() in the Constraint array of Meta section use the function clean for validation rather than constraint.
from django.db import models
from django.core.exceptions import ValidationError
class Account(models.AuditedModel):
"""
Recipient for money
"""
currency = models.CharField('currency', max_length=3, choices=(
('EUR', 'Euro'),
('USD', 'American Dollars'),
('JPY', 'Japan Yen')
))
class Transfer(models.AuditedModel):
"""
The action of moving money from one account to another
"""
account_from = models.ForeignKey(Account, related_name="outgoing", on_delete=models.CASCADE)
account_to = models.ForeignKey(Account, related_name="incoming", on_delete=models.CASCADE)
def clean(self):
if self.account_from.currency != self.account_to.currency:
raise ValidationError("Same currency required for transaction.")
return super().clean()

How to query by joining a Django Model with other, on a non unique column?

I have the following models in my models.py file in my django project
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.conf import settings
class CustomUser(AbstractUser):
pass
# add additional fields in here
class PDFForm(models.Model):
pdf_type=models.IntegerField(default=0)
pdf_name=models.CharField(max_length=100,default='')
file_path=models.FileField(default='')
class FormField(models.Model):
fk_pdf_id=models.ForeignKey('PDFForm', on_delete=models.CASCADE,default=0)
field_type=models.IntegerField(default=0)
field_page_number=models.IntegerField(default=0)
field_x=models.DecimalField(max_digits=6,decimal_places=2,default=0)
field_y=models.DecimalField(max_digits=6,decimal_places=2,default=0)
field_x_increment=models.DecimalField(max_digits=6,decimal_places=2,default=0)
class Meta:
ordering= ("field_page_number", "field_type")
class UserData(models.Model):
fk_user_id=models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE,default=0)
field_type=models.IntegerField(default=0)
field_text=models.CharField(max_length=200,default='')
field_date=models.DateField()
Here is how the models are related
1) a pdfform contains a pdf form and path for it on the file system
2) A pdfform has multiple FormFields in it. Each field has attributes, and the specific one under discussion is field_type
3)The UserData model has user's data, so one User can have multiple rows in this table. This model also has the field_type column.
What I am trying to query is to find out all rows present in the Userdata Model which are present in the FormField Model ( matched with field_type) and that are of a specific PDFForm.
Given that the Many to Many relationship in django models cannot happen between no unique fields, how would one go about making a query like below
select a.*, b.* from FormField a, UserData b where b.fk_user_id=1 and a.fk_pdf_id=3 and a.field_type=b.field_type
I have been going through the documentation with a fine toothed comb, but obviously have been missing how django creates joins. what is the way to make the above sql statement happen, so I get the required dataset?
I think UserData is missing a relation to FormField, but if you had this relation you could do:
UserData.objects.filter(
fk_user_id=1, # Rename this to user, Django wilt automicly create a user_id column
form_field__in=FormField.objects.filter(
fk_pdf_id=<your pdfid> # same as fk_user_id
)
)
Edit updated models
When you use a ForeignKey you don't have to specify the _id or default=0, if you don't always want to fill the field its better to set null=True and blank=True
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.conf import settings
class CustomUser(AbstractUser):
pass
# add additional fields in here
class FieldTypeMixin:
TYPE_TEXT = 10
TYPE_DATE = 20
TYPE_CHOISES = [
(TYPE_TEXT, 'Text'),
(TYPE_DATE, 'Date'),
]
field_type=models.IntegerField(default=TYPE_TEXT, choises=TYPE_CHOISES)
class PDFForm(models.Model):
pdf_type = models.IntegerField(default=0)
pdf_name = models.CharField(max_length=100,default='')
file_path = models.FileField(default='')
class FormField(models.Model, FieldTypeMixin):
pdf_form = models.ForeignKey('PDFForm', on_delete=models.CASCADE)
field_page_number = models.IntegerField(default=0)
field_x = models.DecimalField(max_digits=6,decimal_places=2,default=0)
field_y = models.DecimalField(max_digits=6,decimal_places=2,default=0)
field_x_increment = models.DecimalField(max_digits=6,decimal_places=2,default=0)
class Meta:
ordering = ("field_page_number", "field_type")
class SubmittedForm(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, models.CASCADE)
pdf_form = models.ForeignKey(PDFForm, models.CASCADE)
class SubmittedFormField(models.Model, FieldTypeMixin):
submitted_form = models.ForeignKey(SubmittedForm, models.CASCADE)
form_field = models.ForeignKey(FormField, models.CASCADE, related_name='fields')
field_text = models.CharField(max_length=200,default='')
field_date = models.DateField()
class Meta:
unique_together = [
['submitted_form', 'form_field']
]

Django) How to connect urls-views-models in ManyToMany, OneToMany relationship

I made some models which have ManyToMany, OneToMany relationships, and then I tried to make appropriate class in views.py, so that one can see sub models related to the chosen model.
But in terms of connecting models-serializers-views-urls, I just couldn't figure out how to make it work...
So, what I want to do is : (simplified)
There are 3 models.
Party
People
Food
So Party has ManyToMany relationship with People, and OneToMany relationship with Food. When I reached url like /party_id/people_id, then I want to get specific person's information from given party id.
Here goes my code.
models.py
class Party(models.Model):
par_id = models.TextField()
par_people = models.ManyToManyField(People)
class People(models.Model):
peo_id = models.TextField()
peo_name = models.TextField()
peo_type = models.TextField()
class Food(models.Model):
foo_id = models.TextField()
foo_party = models.ForeignKey(Party, on_delete=models.CASCADE)
serializers.py
class PartySerializer(serializers.ModelSerializer):
class Meta:
model = Party
fields = ('par_id', 'par_people')
# People, Food has same structure...
views.py
class PartyList(generics.ListAPIView):
queryset = Party.objects.all()
serializer_class = PartySerializer
# People, Food has same structure...
urls.py
Here's the part where I got lost
#redundancy reduced...(e.g. import)
urlpatterns = [
path('party/<int:par_id>/<int:peo_id>', views.PartyList.as_view()),
path('party/<int:par_id>/<int:foo_id>', views.PartyList.as_view()),
]
So If I reach website/party/1/3, I want to see person's information(whose peo_id is 3) of party(whose par_id is 1). For food, It goes the same.
Should I make new class in views.py to make it work? But how can url check par_id and foo_id at the same time if I use PartyList view class..? Any help would be much appreciated.
I think something like this should work. The basic principle if work out if using peo_id or foo_id and then filter the queryset on that basis.
def get (self, *args, **kwargs):
id = kwargs.get(peo_id, None)
if id:
self.queryset.filter(par_people__peo_id=id)
else:
id = kwargs.get(foo_id, None)
self.queryset.filter(foo_party=id)

Django annotation on related model

Having these models (simplified):
class UserProfile(models.Model):
user = models.OneToOneField(User)
products = models.ManyToManyField(Product, through='UserProduct')
class Product(models.Model):
title = models.CharField(max_length=100, blank=False)
class UserProduct(models.Model):
user = models.ForeignKey(UserProfile)
product = models.ForeignKey(Product)
class Recipe(models.Model):
ingredients = models.ManyToManyField(Product, through='Ingredient')
class Ingredient(models.Model):
product = models.ForeignKey(Product)
recipe = models.ForeignKey(Recipe)
I need in some cases to get a list of recipes, marked on each ingredient, "whether it is user have that product.". And, maybe other calculated fields, according to given user.
Example of what i want:
>>> Recipe.objects.get_for_user(user=user)[0].ingredients[0].is_user_have
>>> True
But, of course, in other cases i don't want that field attached to ingredients.
I understand that the i need custom manager. But straightforward solution - add "is_user_have" as property to Ingredient model, define custom manager with get_for_user method, call base get_queryset and then in for-loop populate that field - doesn't work.
UPDATE 1
I figured out how to get annotations that i wanted, here is my custom manager for ingredients:
class UserIngredientsManager(models.Manager):
def get_queryset(self):
result = super(UserIngredientsManager, self).get_queryset()
return (result
.annotate(
user_have_count=models.Count(
models.Case(
models.When(
# Hardcoded !!!
product__userproduct__user_id=1,
then=True),
output_field=models.IntegerField())))
.annotate(
is_user_have=models.Case(
models.When(
user_have_count__gt=0,
then=models.Value(True)),
output_field=models.BooleanField(),
default=models.Value(False))))
But there are two problems:
I can't pass user to this manager (its hardcoded for testing)
I can't create proxy model for situtations when i want this annotations (see below), it only works when i replace default manager on Ingredient model.
This code doesn't work, for ingredients default related manager used instead:
class RecipeWithUserInfo(Recipe):
class Meta:
proxy = True
objects = UserRecipesManager()
ingredients = UserIngredientsManager()
It works only when i replace default manager on Ingredient model (but that not what i want):
class Ingredient(models.Model):
...
objects = UserIngredientsManager()

Django date query from newest to oldest

I am building my first Django program from scratch and am running into troubles trying to print out items to the screen from newest to oldest.
My model has an auto date time field populated in the DB as so:
Model
from django.db import models
from django.contrib.auth.models import User
from django.conf import settings
from django.utils import timezone
class TaskItem(models.Model):
taskn = models.CharField(max_length = 400)
usern = models.ForeignKey(User)
#Created field will add a time-stamp to sort the tasks from recently added to oldest
created_date = models.DateTimeField('date created', default=timezone.now)
def __str__(self):
return self.taskn
What is the line of code that would be abel to sort or print this information in order from newest creation to oldest?
Want to implement it into this call:
taskitems2 = request.user.taskitem_set.all().latest()[:3]
ordered_tasks = TaskItem.objects.order_by('-created_date')
The order_by() method is used to order a queryset. It takes one argument, the attribute by which the queryset will be ordered. Prefixing this key with a - sorts in reverse order.
By the way you also have Django's created_at field at your disposal:
ordered_tasks = TaskItem.objects.order_by('-created_at')
You can set your ordering in model Meta class. This will be the default ordering for the object,for use when obtaining lists of objects.
class TestModel(models.Model):
...
created_at = models.DateField()
....
class Meta:
ordering = ['-created_at']
Or you can apply ordering to specific queryset.
TestModel.objects.order_by('-created_at')