How to test if an instance of a django model was created? - django

I am testing an api for a game and have some issues with a model.
This is my model:
models.py
class Gamesession(models.Model):
gamemode = models.ForeignKey(Gamemode, on_delete=models.CASCADE, null=True)
user = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True)
gametype = models.ForeignKey(Gametype, on_delete=models.CASCADE)
created = models.DateTimeField(editable=False)
objects = models.Manager()
This is the test suite:
test_models.py
def setUp(self):
self.user = CustomUser.objects.create(id=1, username="carina")
self.gametype = Gametype.objects.create(id=1, name="NewGame", rounds=5, round_duration=60, enabled=True)
self.gamesession_user = self.user
self.gamesession_gametype = self.gametype
self.gamesession_created = datetime.utcnow().replace(tzinfo=pytz.UTC)
self.gamesession = Gamesession.objects.create(id=1,
user=self.gamesession_user,
gametype=self.gamesession_gametype,
created=self.gamesession_created)
def test_create_gamesession(self):
gamesession = Gamesession.objects.create(gametype=self.gametype)
assert gamesession.gametype == self.gamesession_gametype
Running my tests keeps retrieving the error: GamesessionTests::test_create_gamesession - django.db.utils.IntegrityError: null value in column "created" of relation "frontend_games
How can I solve this? Is there a better way to test if an instance of the model is created?

This happens because you defined created as a field that is not null=True and without any default=…, so that means that it does not assign a proper value to it. You thus can construct the object with:
def test_create_gamesession(self):
gamesession = Gamesession.objects.create(
gametype=self.gametype,
gamesession_created=self.gamesession_created
)
assert gamesession.gametype == self.gamesession_gametype
You probably do not want to specify a value anyway, you can make use of auto_now_add=True [Django-doc] to specify that Django should fill in the timestamp when creating the object:
class Gamesession(models.Model):
# …
created = models.DateTimeField(auto_now_add=True)
this will also make the field non-editable.

Related

Properly update Django model's foreign key property

I have a Django Model named EmailSendingTask. This is the whole model-
class EmailSendingTask(models.Model):
order = models.OneToOneField(Order, on_delete=models.CASCADE, related_name='order')
status = EnumChoiceField(SetupStatus, default=SetupStatus.active)
time_interval = EnumChoiceField(TimeInterval, default=TimeInterval.five_mins)
immediate_email = models.OneToOneField(PeriodicTask, on_delete=models.CASCADE, null=True, blank=True, related_name='immediate_email')
scheduled_email = models.OneToOneField(PeriodicTask, on_delete=models.CASCADE, null=True, blank=True, related_name='scheduled_email')
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = 'EmailSendingTask'
verbose_name_plural = 'EmailSendingTasks'
def __str__(self) -> str:
return f'EmailSendingTask: Order = {self.order}'
The immediate_email and scheduled_email fields are responsible for holding two PeriodicTask objects.
I have created a function called disable_scheduled_email, which is responsible for disabling the scheduled_email's periodic task. The detail of the function is here-
def disable_scheduled_email(self):
print(f'Disabling scheduled email...')
self.scheduled_email.enabled = False
self.save(update_fields=['scheduled_email'])
Now, whenever I call this function and print the value of the self.scheduled_email.enabled, I find it False. But, when I try to look at the Django Admin site, the periodic task's enabled value remains as True. Why is it happening?
After some experiments into the Django Shell I have found out that, I was not specifically calling save() to the foreign key (scheduled_email). I have just added self.scheduled_email.save() into the disable_scheduled_email function. So, the whole function became like:
def disable_scheduled_email(self):
print(f'Disabling scheduled email...')
self.scheduled_email.enabled = False
# self.save(update_fields=['scheduled_email'])
self.scheduled_email.save() #instead of self.save(...), wrote this

Best approach to create a global search field for a postgres table in Django based GraphQL API?

I am working with an Angular UI with a Django-graphene GraphQL API and a postgres db.
Currently I have implemented a functionality to arrive at a global search field by adding a "searchField" for each table and updating it with each create and update of an item in that table. And using the graphql filter, every time a user would do a global search, I would just filter hte searchField for the query. I am very new to Django so I'm not sure if this is an efficient way to go about this, but this is what I have:-
Create mutation
class CreateUser(graphene.Mutation):
class Arguments:
input = UserInput(required=True)
ok = graphene.Boolean()
user = graphene.Field(UserType)
#staticmethod
def mutate(root, info, input=None):
ok = True
searchField = input.username if input.username is not None else "" + \
input.title if input.title is not None else "" + \
input.bio if input.bio is not None else ""
user_instance = User(user_id=input.user_id, title=input.title, bio=input.bio,
institution_id=input.institution_id, searchField=searchField)
user_instance.save()
return CreateUser(ok=ok, user=user_instance)
Update mutation
class UpdateUser(graphene.Mutation):
class Arguments:
id = graphene.Int(required=True)
input = UserInput(required=True)
ok = graphene.Boolean()
user = graphene.Field(UserType)
#staticmethod
def mutate(root, info, id, input=None):
ok = False
user = User.objects.get(pk=id)
user_instance = user
if user_instance:
ok = True
user_instance.name = input.name if input.name is not None else user.name
user_instance.avatar = input.avatar if input.avatar is not None else user.avatar
user_instance.institution_id = input.institution_id if input.institution_id is not None else user.institution_id
user_instance.title = input.title if input.title is not None else user.title
user_instance.bio = input.bio if input.bio is not None else user.bio
user_instance.searchField = user_instance.searchField + \
user_instance.name if user_instance.name is not None else ""
user_instance.searchField = user_instance.searchField + \
user_instance.title if user_instance.title is not None else ""
user_instance.searchField = user_instance.searchField + \
user_instance.bio if user_instance.bio is not None else ""
user_instance.save()
return UpdateUser(ok=ok, user=user_instance)
return UpdateUser(ok=ok, user=None)
Not sure if you can tell, but I'm very new to Python and Django. And what I'm doing here is, every time a user record is created or updated, I am maintaining a field in the table called searchField that will have a string of all the fields in the table that I want the global search to touch upon. I've manually written each line like that. Not sure if this is in line with best practice.
Here's the user model.
User model
class User(AbstractUser):
name = models.CharField(max_length=50)
email = models.EmailField(blank=False, max_length=255, unique=True)
avatar = models.CharField(max_length=250, blank=True, null=True)
institution = models.ForeignKey(
'Institution', on_delete=models.PROTECT, blank=True, null=True)
title = models.CharField(max_length=150, blank=True, null=True)
bio = models.CharField(max_length=300, blank=True, null=True)
searchField = models.CharField(max_length=600, blank=True, null=True)
USERNAME_FIELD = 'username'
EMAIL_FIELD = 'email'
So there are a couple of things here.
First I know that I'm doing something wrong in terms of setting up the updating of the searchField in the update mutation resolver method. That's due to my poor knowledge of Python. So if someone could sort out what I'm doing wrong that would be great.
I am doing this completely on my own, I have no idea if this approach is actually a good strategy in terms of efficiency or if there's already a good solution for graphql based django api. So if there is please point me to it.
Thank you.
Yes, if you want to update your searchField you have to build it from scratch. If you use old data and append new data, it will return the record if you search for old data and that's something you want to avoid.
Regarding your second question, you can use django-filter.
Steps:
install django-filter
create a base filterset class and add search field to it
class CustomFilterSet(filters.FilterSet):
search = filters.CharFilter(method='search_filter')
def search_filter(self, queryset, field_name, value):
return queryset
create filterset class for your models inheriting from your custom filterset class and override search_filter method. In order to perform or operations on querysets use Q object. In your case something like:
class UserFilterSet(CustomFilterSet):
...
def search_filter(self, queryset, field_name, value):
return queryset.filter(
Q(username__icontains=value) |
Q(title__icontains=vallue) |
Q(bio__icontains=value)
)
which looks for the value in specified fields regardless of
being lower/upper case
create graphql types and add related filterset_class to Meta class:
class UserType(DjangoObjectType):
class Meta:
model = User
filterset_class = UserFilterSet
interfaces = (relay.Node, )
Now you can search globally for the specified fields by passing just a single string.

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

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

Django Q bad query logic

I'm trying to create a manager that has a method 'active_or_users' to retrieve all accounts that are active, or that an user has created. An active account has a start date that is either today, or somewhere in the past, and a end date that is somewhere in the future. Right now the active_or_users method works, however it returns duplicates of the same object. It's returning three copies of a user created active job. This is less than ideal.
from django.db.models import Q
from django.db import models
from django.contrib.auth.models import User
class ActiveJobs(models.Manager):
def active(self):
return super(ActiveJobs, self).get_query_set().\
filter(publications__publish_on__lte=date.today(),
publications__end_on__gt=date.today())
def active_or_users(self, user):
return super(ActiveJobs, self).get_query_set().\
filter((Q(publications__publish_on__lte=date.today()) &
Q(publications__end_on__gt=date.today())) | Q(creator=user))
class Job(models.Model):
title = models.CharField(max_length=100)
slug = models.SlugField(blank=True, null=True)
creator = models.ForeignKey(User)
objects = ActiveJobs()
class JobPublicationRecord(models.Model):
job = models.ForeignKey('Job', related_name='publications')
publish_on = models.DateField(auto_now=False)
end_on = models.DateField(auto_now=False, auto_now_add=False,
blank=True, null=True)
To put the comments into an answer
With the OR query, an instance will be returned for every hit of the query. I.e: an instance if a Job is created by user and another instance of the same job if also in the date range specified, etc.
So to fix this, change the method active_or_users to:
def active_or_users(self, user):
return super(ActiveJobs, self).get_query_set().filter(
(Q(publications__publish_on__lte=date.today()) &
Q(publications__end_on__gt=date.today())) | Q(creator=user)).distinct()

Django: How to save inherited models?

class Conversation(models.Model):
contact = models.ForeignKey(Contact)
conversation_datetime = models.DateTimeField()
notes = models.TextField(_(u'Notes'), blank=True)
def __unicode__(self):
return self.conversation_datetime
class Conversation_history(Conversation):
log_date_time = CreationDateTimeField()
def __unicode__(self):
return self.conversation_datetime
Not sure if this is the best to do it, but I was hoping to create a history table of each major mode, so that I can follow what the customer was doing and help them in a support case.
I have created a new model based on the original model. But when I save an instance of the new model, the original table gets populated. I have no idea why.
call = Conversation(contact='', conversation_datetime = '', notes='')
call.save()
ch = Conversation_history(contact='', conversation_datetime = '', notes='')
ch.save()
Because you haven't declared your Conversation model to be abstract. You're using multi-table inheritance. Have a look at the docs.
If you want all the data stored in your child then you should do something like -
class ConversationBase(models.Model):
contact = models.ForeignKey(Contact)
conversation_datetime = models.DateTimeField()
notes = models.TextField(_(u'Notes'), blank=True)
class Meta:
absract = True
class Conversation(ConversationBase):
pass
class ConversationHistory(ConversationBase):
log_date_time = CreationDateTimeField()