Django check constraint for datetime - django

I have this model:
class MyModel(models.Model):
creation_datetime = models.DateTimeField()
expiration_datetime = models.DateTimeField()
class Meta:
constraints = [
CheckConstraint(
check=####
)
]
I am trying to write a check constraint to ensure that expiration_dateime > creation_dateime. I am unable to find a way to do it correctly with Q objects, help is appreciated.

For Django 3.1:
from django.db import models
from django.db.models import F, Q
class MyModel(models.Model):
creation_datetime = models.DateTimeField()
expiration_datetime = models.DateTimeField()
class Meta:
constraints = [
models.CheckConstraint(
check=Q(expiration_dateime__gt=F('creation_datetime')),
name="mycustomconstraint_check1"
)
]
Using F expresions: https://docs.djangoproject.com/en/3.1/ref/models/expressions/#f-expressions.
And more specific: https://docs.djangoproject.com/en/3.1/topics/db/queries/#using-f-expressions-in-filters.

Model.clean()
This method should be used to provide custom model validation, and to
modify attributes on your model if desired. For instance, you could
use it to automatically provide a value for a field, or to do
validation that requires access to more than a single field
Note, however, that like Model.full_clean(), a model’s clean()
method is not invoked when you call your model’s save() method.
https://docs.djangoproject.com/en/3.0/ref/models/instances/#django.db.models.Model.clean

Related

Django-filters query params conflict

I am trying to work with filtering db by query params from the link that looks like this:
{{url}}/api/books?author="XXX"&from=2003&to=2051&acquired=true
I already handled author and acquired params but am stuck with from and to. My filter.py looks like this:
class BookFilter(django_filters.FilterSet):
author = django_filters.CharFilter(field_name="authors__fullname", lookup_expr="icontains")
from = django_filters.NumberFilter(field_name="published_year", lookup_expr="gte")
to = django_filters.NumberFilter(field_name="published_year", lookup_expr="lte")
acquired = django_filters.BooleanFilter(field_name="acquired")
class Meta:
model = Book
fields = [
"author",
"from",
"to",
"acquired"
]
I am looking for a way to assign these query params without overwriting key words (from and to) which is obviously a terrible idea.
You don't need to define special BookFilter class. This task can simply done on view level. Just define a DateFilter class to filter according to the dates, and the other
type of filters like search by name or order by price can be implemented on view level. (Make sure your model have a created_at field).
Here is an example
from django_filters import rest_framework as rest_filters
from apps.yourappname.models import YourModel
from django_filters import rest_framework as rest_filters
class DateFilter(rest_filters.FilterSet):
created_at = rest_filters.DateFromToRangeFilter()
class Meta:
model = YourModel
fields = ['created_at']
class YourAPIView(ListAPIView):
serializer_class = YourModelSerializer
filter_backends = [filters.SearchFilter, filters.OrderingFilter,
rest_filters.DjangoFilterBackend, ]
filter_class = DateFilter
search_fields = ['authors__fullname' ]
ordering_fields = [ 'created_at', ]
def get_queryset(self):
dataset_queryset =
YourModel.objects.filter(id=self.request.user.id)
return dataset_queryset
When you filter by date, your URL will look like this:
http://127.0.0.1:8000/api/yourappname/id/files/?created_at_after=2022-04-19&created_at_before=2022-05-28
For anyone that may have same problem:
I managed to create a workaround in which I create copy of querydict, pop() "from" and "to" values and assign them to newly created "from_date" and "to_date" keys.

How to define two Django fields unique in certain conditions

I want to make Django Model fields unique with two fields(values) in some conditions.
there's two fields: 'team', 'type'. And I want to make team manager unique
For Example:
team=1, type='manager'
team=1, type='manager'
-> Not available
team=1, type='manager'
team=1, type='member'
team=1, type='member'
team=2, type='manager'
-> Available
I think unique_together('team', 'type') won't work properly with this situation.
How can I make this with Django Model?
Here's my model below:
class myModel(models.Model):
team = models.ForeignKey('Team', on_delete=models.CASCADE)
type = models.CharField(max_length=10, default='member')
class Meta:
db_table = 'my_models'
I think, You need to use UniqueConstraint for your application which work perfect in kind of situation.
class myModel(models.Model):
team = models.ForeignKey('Team', on_delete=models.CASCADE)
type = models.CharField(max_length=10, default='member')
class Meta:
db_table = 'my_models'
constraints = [
models.UniqueConstraint(fields=['team', 'type'], name='unique_team')
]
you can also refer this link for more understanding. and let me know if following solution will work.
Given that there is a deprecation warning in the documentation (based on 3.2 docs) for unique_together, I think it's worth showing that this can be done using UniqueConstraint. I believe that the key missing ingredient from the previous answer is the use of UniqueConstraint.condition, like so:
from django.db import models
from django.db.models import Q, UniqueConstraint
class myModel(models.Model):
team = models.ForeignKey('Team', on_delete=models.CASCADE)
type = models.CharField(max_length=10, default='member')
class Meta:
db_table = 'my_models'
constraints = [
UniqueConstraint(
fields=['team', 'type'],
name='unique_team',
condition=Q(type='manager')
)
]

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']
]

How to specify which value is returned for a foreign field when serialising data in a view in Django

I have a model containing a ForeignKey to another model. I am attempting to serialize this model and want to control what field is returned for the foreignkey field. See below:
models.py
class Surveyor(models.Model):
num = models.CharField(max_length=3)
name = models.CharField(max_length=250)
class Anblsrecord(models.Model):
...
sur_num = models.ForeignKey(Surveyor, on_delete=models.CASCADE)
views.py
def anbls_points(request):
points_as_geojson = serialize('geojson', Anblsrecord.objects.all()[:5], fields=(... 'sur_num'))
return JsonResponse(json.loads(points_as_geojson))
When I view this I get:
... "sur_num": 1 ...
where the "1" is "num" from Surveyor class. I want to return "name".
I looked at https://docs.djangoproject.com/en/2.2/topics/serialization/ which talks about multi-table inheritance, but I can't find anything for a related table.
Any help would be greatly appreciated.
Django Rest Framework serializers with django-rest-framework-gis worked:
serializers.py
from anblsrecords import models
from rest_framework_gis.serializers import GeoFeatureModelSerializer
class AnblsrecordSerializer(GeoFeatureModelSerializer):
sur_name = serializers.CharField(source='sur_num.name')
class Meta:
model = models.Anblsrecord
geo_field = "geom"
fields = (
...
'sur_name',
)
views.py
from rest_framework import generics
class ListAnbls_points(generics.ListCreateAPIView):
queryset = Anblsrecord.objects.all()[:5]
serializer_class = serializers.AnblsrecordSerializer
This returns:
"properties": {
...,
"sur_name": "Name of Surveyor",...}, and includes the geometry feature.

How can I relate two models (django tutorial's Poll and Choice) in a Tastypie API

I'm trying relate two resources (models) in an API using Tastypie but I'm getting an error.
I've followed the django tutorial and used:
models.py
from django.db import models
class Poll(models.Model):
question = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
class Choice(models.Model):
poll = models.ForeignKey(Poll)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
I tried to create a link between the Poll and Choice based on this stackoverflow answer and wrote the following code:
api.py
class ChoiceResource(ModelResource):
poll = fields.ToOneField('contact.api.PollResource', attribute='poll', related_name='choice')
class Meta:
queryset = Choice.objects.all()
resource_name = 'choice'
class PollResource(ModelResource):
choice = fields.ToOneField(ChoiceResource, 'choice', related_name='poll', full=True)
class Meta:
queryset = Poll.objects.all()
resource_name = 'poll'
When I go to: 127.0.0.1:8088/contact/api/v1/choice/?format=json
Everything works as it should. For example one of my choices links to the right poll:
{
"choice_text": "Nothing",
"id": 1,
"poll": "/contact/api/v1/poll/1/",
"resource_uri": "/contact/api/v1/choice/1/",
"votes": 6
}
When I go to: 127.0.0.1:8088/contact/api/v1/poll/?format=json
I get:
{
"error": "The model '<Poll: What's up?>' has an empty attribute 'choice' and doesn't allow a null value."
}
Do I need to use the fields.ToManyField instead or do I need to change my original model?
Tastypie recommends against creating reverse relationships (what you're trying to do here the relationship is Choice -> Poll and you want Poll -> Choice), but if you still wanted to, you can.
Excerpt from the Tastypie docs:
Unlike Django’s ORM, Tastypie does not automatically create reverse
relations. This is because there is substantial technical complexity
involved, as well as perhaps unintentionally exposing related data in
an incorrect way to the end user of the API.
However, it is still possible to create reverse relations. Instead of
handing the ToOneField or ToManyField a class, pass them a string that
represents the full path to the desired class. Implementing a reverse
relationship looks like so:
# myapp/api/resources.py
from tastypie import fields
from tastypie.resources import ModelResource
from myapp.models import Note, Comment
class NoteResource(ModelResource):
comments = fields.ToManyField('myapp.api.resources.CommentResource', 'comments')
class Meta:
queryset = Note.objects.all()
class CommentResource(ModelResource):
note = fields.ToOneField(NoteResource, 'notes')
class Meta:
queryset = Comment.objects.all()