I have a simple django model with an integer field status:
from django.db import models
class MyObject(models.Model):
dateDownloaded = models.DateField(null=True)
dateShared = models.DateTimeField(null=False)
sharedBy = models.CharField(max_length=100, null=False)
sharedTo = models.CharField(max_length=100, null=False)
token = models.CharField(max_length=200, null=False)
status = models.IntegerField(null=False, default=0)
def save(self, *args, **kwargs):
if not self.pk: # First time saved
self.status = 0
self.token = get_random_string(length=32)
super(MyObject, self).save(*args, **kwargs)
I can add objects to my model and I have a simple helper that counts the number of created objects. Now I also have a call that does the following:
def resetStatus(request):
myObjects = MyObject.objects.all()
for myObject in myObjects:
myObject.status = 0
myObject.save()
return HttpResponse("reset done")
Issue is, after calling this, from time to time all the objects from my database have disappeared. Maybe I have done something wrong with my objects in between but I have no idea what it could be. How can I go about debugging this ?
Related
I have an exam model that whenever an instance is created, instances of the Question model to the number that is specified in the Exam are created(using post_save signal). Also, I have a Go code that whenever a request is sent, fills out 3 fields of the Question model. My problem is how can I send this request in the signal part.
The codes are as followed:
models.py:
class Exam(models.Model):
title = models.CharField(max_length=255)
subject = models.CharField(max_length=255, default='')
organizer = models.CharField(max_length=255, default='...')
description = models.TextField(max_length=1000)
created_at = models.DateTimeField(auto_now_add=True)
duration = models.DurationField()
number_of_questions = models.PositiveSmallIntegerField()
order = models.IntegerField(default=0)
def __str__(self):
return self.title
class ExamQuestion(models.Model):
exam = models.ForeignKey('ExamApply', on_delete=models.CASCADE)
question_template = models.ForeignKey(QuestionTemplate, on_delete=models.CASCADE)
text = models.TextField(max_length=5000, null=True, blank=True)
question_params = models.JSONField(null=True, blank=True)
answer_choices = models.JSONField(null=True, blank=True)
answer_given = models.JSONField(default=dict, null=True, blank=True)
correct_answer = models.JSONField(null=True, blank=True)
data = models.JSONField(null=True, blank=True)
is_correct = models.BooleanField(null=True)
order = models.IntegerField(null=True, blank=True)
def __str__(self):
return str(self.id)
class ExamApply(models.Model):
class Status(models.TextChoices):
CREATED = 'CR', 'Created'
STARTED = 'ST', 'Started'
FINISHED = 'FN', 'Finished'
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
exam = models.ForeignKey(Exam, on_delete=models.CASCADE)
start_date = models.DateTimeField()
end_date = models.DateTimeField()
status = models.CharField(max_length=2, choices=Status.choices, default=Status.CREATED)
def get_score(self):
score = ExamQuestion.objects.filter(exam=self, answer_given=F('correct_answer')).count()
return score
signals.py:
#receiver(post_save, sender=ExamApply)
def create_examapply_examquestion(sender, instance, created, **kwargs):
if created:
for _ in range(instance.exam.number_of_questions):
ExamQuestion.objects.create(exam=instance)
id = ExamQuestion.objects.all().last().id
return redirect('/question/' + str(id) + '/') #doesnt work
#receiver(post_save, sender=ExamApply)
def save_examapply_examquestion(sender, instance, created, **kwargs):
instance.exam.save()
urls.py related to the part I want:
urlpatterns = [
path('questions/<int:pk>/', UpdateQuestionAPI.as_view()),
]
views.py:
class UpdateQuestionAPI(generics.RetrieveUpdateDestroyAPIView):
queryset = ExamQuestion.objects.all()
serializer_class = IntegrateQuestionSerializer
lookup_field = 'pk'
def get(self, request, *args, **kwargs):
question = ExamQuestion.objects.filter(pk=kwargs['pk'])
serializer = ExamQuestionSerializer(question, many=True)
return Response(serializer.data)
def update(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response({"message": "updated successfully"})
else:
return Response({"message": "failed", "details": serializer.errors})
serializers.py:
class IntegrateQuestionSerializer(serializers.ModelSerializer):
class Meta:
model = ExamQuestion
fields = ['question_params', 'answer_choices', 'correct_answer',]
class ExamQuestionSerializer(serializers.ModelSerializer):
title = serializers.SerializerMethodField()
class Meta:
model = ExamQuestion
fields = ['title']
def get_title(self, obj):
return obj.exam.exam.title
I had the idea of using redirect(to the update view), but it doesn't work.
First of all, it makes no sense to use request in the signal part.
This code is related to the model and the orm and is not responsible of the request part. That's the view work.
The signal you are using is related to the creation of the ExamApply model. The view you posted doesn't create a ExamApply so it will not get called at all.
Just so you know, usually signals are avoided because they lead to complicated code. To quote the documentation “Signals give the appearance of loose coupling, but they can quickly lead to code that is hard to understand, adjust and debug. Where possible you should opt for directly calling the handling code, rather than dispatching via a signal.”
Here you have multiple issues in your signals, you have a return in a loop, that will stop the loop after the first iteration. Then you return an HttpResponse (in the form of redirect) but you are not in a view so it doesn't make sense.
It's not clear what you want to do because the view and the serializer you posted are not related to the creation of ExamApply as I understand it what you could do (one of):
Override the save method of ExamApply to create the questions.
Or handle the creation of questions in the view that create the ExamApply
For example:
def save(self, *args, **kwargs):
if not self.pk:
# not self.pk means a creation.
for _ in range(self.exam.number_of_questions):
question = ExamQuestion.objects.create(exam=self.exam)
# You might want to link question to user / exam apply here?
self.exam.question_set.add(question)
super().save(*args, **kwargs)
In your ExamApply creation view or serializer you might want to return the questions id, if you use DRF it's trivial to do so: https://www.django-rest-framework.org/api-guide/relations/#primarykeyrelatedfield
But you might want to link questions to ExamApply and/or user and not just Exam.
On a side note, for the future, if you need the created object id you should use
question = ExamQuestion.objects.create(exam=instance)
question.id
I have 2 classes
class Service(models.Model):
service_name = models.CharField(max_length=15, blank=False)
service_time = models.IntegerField(blank=False)
class Appointment(models.Model):
name = models.CharField(max_length=15)
service_selected = models.ManytoManyField(Service, blank=True)
total_time = models.IntegerField(null=True, blank=True)
What Im trying to do is that after a user has selected the services and created an appointment, each of the services' service_time will be added up to equal total_time
I know I have to use something along the lines of
#in class Appointment
def save(self, *args, **kwargs):
self.total_time += self.service_selected.service_time #it needs to add up all of the service_time from each service selected but that's where Im lost
super(Appointment, self).save(*args, **kwargs)
But I don't know how to get the service_time value from each of the service_name that were selected, or how to query for every service_selected chosen in that appointment's object so that I can add up those values.
edit:
Had to change
total_time = models.IntegerField(blank=False)
to
total_time = models.IntegerField(blank=False, null=False, default=0)
for the answer to work
You can do it as follows:
#in class Appointment
def save(self, *args, **kwargs):
self.total_time += Service.objects.all().aggregate(total_time=Sum('service_time'))['total_time']
# You will have to run a query to get the data and aggregate according to that. You may change the query according to your needs.
super(Appointment, self).save(*args, **kwargs)
I want to create a constraint of the form code = <min_weight> - <max_weight> <weight_unit>
how do i do that i just learned it
class Size(models.Model):
code = models.CharField(max_length=200, primary_key=True)
uni = [("GC", "Gram/Con"), ("KC", "Kg/Con")]
min_weight = models.IntegerField(blank=False, null=False)
max_weight = models.IntegerField(blank=False, null=False)
weight_uni = models.CharField(max_length=10, choices=uni, default="KC")
def __str__(self):
return self.code
Since code is 100% determined by values of other fields, there is no need to store it as separate field in db. Rather, make it a property, like this:
class Size(models.Model):
uni = [("GC", "Gram/Con"), ("KC", "Kg/Con")]
min_weight = models.IntegerField(blank=False, null=False)
max_weight = models.IntegerField(blank=False, null=False)
weight_uni = models.CharField(max_length=10, choices=uni, default="KC")
#property
def code(self):
return '%s - %s %s' % (self.min_weight, self.max_weight, self.weight_uni)
def __str__(self):
return self.code
You can override the save() method and configure the following:
def save(self, *args, **kwargs):
self.code = self.min_weight - self.max_weight*self.weight_unit
return super(Size, self).save(*args, **kwargs)
That way each time a save is called(it is called when a value is changed) it will reevaluate the "code" field and update it if necessary.
Here is an example how to use constraints but I don't think that you would be able to achieve the same result:
class Meta:
constraints = [
models.CheckConstraint(
name='SimpleConstaint',
check=(models.Q(sold_units__gte=0)&models.Q(sold_units__lte=100))
)
]
I have a super class for my models as below:
class BaseModel(models.Model):
""" BaseClass vase aksare model ha """
def __init__(self, *args, **kwargs):
super(BaseModel, self).__init__(args, kwargs)
print('******> base model __init__')
status = models.IntegerField(default=1)
is_deleted = models.BooleanField(default=False)
create_user = models.ForeignKey(User, related_name="%(app_label)s_%(class)s_creator_related")
create_date = models.DateTimeField()
update_date = models.DateTimeField()
update_user = models.ForeignKey(User, related_name="%(app_label)s_%(class)s_updater_related")
class Meta:
abstract = True
def validate(self):
print('^^^^^^^^^^^^^^^^^^^^^^^^^^^^base validation')
and I have a profile model as below:
class Profile(BaseModel):
def __init__(self, *args, **kwargs):
super(Profile, self).__init__(args, kwargs)
""" User profile """
user = models.OneToOneField(User, related_name='profile')
mobile = models.CharField(max_length=25, null=True)
firstname_en = models.CharField(max_length=500, null=True)
lastname_en = models.CharField(max_length=500, null=True)
gender = models.IntegerField(default=0)
birth_date = models.DateTimeField(null=True)
edu_bg = models.ForeignKey('Category', related_name="profile__edu_bg", null=True)
region = models.ForeignKey('Category', related_name="profile__region", null=True)
credit = models.DecimalField(default=0, decimal_places=6, max_digits=15)
key = models.TextField(null=True)
secret = models.TextField(null=True)
I have an error when I want to insert a new userProfile as below:
TypeError: int() argument must be a string, a bytes-like object or a number, not 'tuple'.
then print the vars(userprofileObject) and realized that 'id': ((), {}), however, I have not set it. When I removed the __init__ functions or set id to None in insertion code, problem solved.
Any idea?
I need those __init__ and also don't want to set id=None in my code
This is how django's models work. You shouldn't change their __init__ method.
This is why
You may be tempted to customize the model by overriding the __init__ method. If you do so, however, take care not to change the calling signature as any change may prevent the model instance from being saved. Rather than overriding __init__, try using one of these approaches:
# Add a classmethod on the model class:
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=100)
#classmethod
def create(cls, title):
book = cls(title=title)
# do something with the book
return book
book = Book.create("Pride and Prejudice")
Source https://docs.djangoproject.com/en/1.10/ref/models/instances/#django.db.models.Model
Also read this Writing a __init__ function to be used in django model
How to set default charfield in lowercase?
This is my model:
class User(models.Model):
username = models.CharField(max_length=100, unique=True)
password = models.CharField(max_length=64)
name = models.CharField(max_length=200)
phone = models.CharField(max_length=20)
email = models.CharField(max_length=200)
def __init__(self, *args, **kwargs):
self.username = self.username.lower()
I tried the __init__ but it doesn't work. I want to make the username in lowercase every time new record saved. Thanks.
While overwriting save() method is a valid solution. I found it useful to deal with this on a Field level as opposed to the Model level by overwriting get_prep_value() method.
This way if you ever want to reuse this field in a different model, you can adopt the same consistent strategy. Also the logic is separated from the save method, which you may also want to overwrite for different purposes.
For this case you would do this:
class NameField(models.CharField):
def get_prep_value(self, value):
return str(value).lower()
class User(models.Model):
username = models.CharField(max_length=100, unique=True)
password = models.CharField(max_length=64)
name = NameField(max_length=200)
phone = models.CharField(max_length=20)
email = models.CharField(max_length=200)
Just do it in the save method. ie, override the save method of Model class.
def save(self, *args, **kwargs):
self.username = self.username.lower()
return super(User, self).save(*args, **kwargs)
signals also works
from django.db.models.signals import pre_save
#receiver(pre_save, sender=YourModel)
def to_lower(sender, instance=None, **kwargs):
instance.text = instance.text.lower() if \
isinstance(instance.text, str) else ''
In my case I had a recipient_name field that I needed to make all lower case when it is stored on DB
class LowerField(models.CharField):
def get_prep_value(self, value):
return str(value).lower()
class Recipients(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='recipients', on_delete=models.CASCADE, )
recipient_account_number = models.IntegerField()
recipient_name = LowerField(max_length=30)
recipient_bank_name = models.CharField(max_length=30)
date = models.DateTimeField(auto_now=True, verbose_name='Transaction Date')
class Meta:
ordering = ['-date']
def __str__(self):
return self.recipient_name
def get_absolute_url(self):
return reverse('recipient-detail', kwargs={'pk': self.pk})
Similarly, you can apply to another table called Transactions in your app, like this
class Transactions(models.Model):
transaction_type = (
('transfer', 'Transfer'),
)
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='transactions', on_delete=models.CASCADE, )
bank_name = LowerField(max_length=50)
def save(self, force_insert=False, force_update=False):
self.YourFildName = self.YourFildName.upper()
super(YourFomrName, self).save(force_insert, force_update)