django restful filtering by derived field - django

I was wondering if I can use a serializer to add two columns of a database, package it, and as a bonus FILTER by it.
models.py
class Snippet(models.Model):
time1= models.DateTimeField()
time2 = models.DateTimeField()
serializers.py
class SnippetSerializer(serializers.ModelSerializer):
sometime= UnixEpochDateField(source = 'time1') # special serializer that converts a datetime to epoch (below)
sometime2= UnixEpochDateField(source = 'time2') # special serializer that converts a datetime to epoch (below)
sometime_SUM = sometime + sometime2 # THIS DOES NOT WORK!
class Meta:
model = Snippet
fields = ('sometime_SUM')
class UnixEpochDateField(serializers.DateTimeField):
def to_native(self, value):
""" Return epoch time for a datetime object or ``None``"""
import time
try:
return calendar.timegm(value.utctimetuple())
except (AttributeError, TypeError):
return None
def from_native(self, value):
import datetime
try:
value = float(value)
except:
raise serializers.ValidationError("Not a number. Input: %s %s"%(type(value), value))
if value < 0:
raise serializers.ValidationError("Cannot be less than 0. Input: %s"%value)
return datetime.datetime.utcfromtimestamp(float(value))
views.py
class SnippetList(APIView):
"""
List all snippets, or create a new snippet.
"""
filter = ('sometime_SUM') # THIS DOES NOT WORK!
def get(self, request, format='json'):
snippets = Snippet.objects.all()
filter = SnippetFilter(request.GET, queryset = snippets) # THIS DOES NOT WORK!
serializer = SnippetSerializer(filter, many=True)
return Response(serializer.data)
I have highlighted the code that doesn't work that I naively thought would work.
The url command I want to use is /someurl/snippets/?sometime_SUM>4
EDIT:
Apparently, I can't filter by a serialize field. So let me modify my question to, can I filter by time1, except when I input the time from my website to the API, i am using EPOCH time, instead of the time1, DATETIME
Thanks #mariodev

Related

Querying for models based on time range in Django REST Framework

I have a DB with models that each have two timestamps.
class Timespan(models.Model):
name = models.CharField(null=False)
start_time = models.DateTimeField(null=False)
end_time = models.DateTimeField(null=False)
I want to be able to query for these objects based on a timestamp range. A GET request would also have a start and end time, and any Timespans that overlap would be returned.
However I'm not sure how to construct a GET Request to do this, nor the View in Django.
Should the GET request just use url parameters?
GET www.website.com/timespan?start=1000&end=1050
or pass it in the body? (if it even makes a difference)
My view currently looks like this:
class TimespanViewSet(OSKAuthMixin, ModelViewSet):
queryset = Timespan.objects.filter()
serializer_class = TimespanSerializer
Which allows me to return obj by ID GET www.website.com/timestamp/42.
I expect I'll need a new viewset for this query. I know how to add a ViewSet with a nested urlpath, but shouldn't there be a way to send a request to /timespan the inclusion of a "start" and "end" parameter changes what is returned?
you can do the conversion and many ways but I guess the easiest way is like this:
...
from django.utils import timezone
...
class TimespanViewSet(OSKAuthMixin, ModelViewSet):
queryset = Timespan.objects.all()
serializer_class = TimespanSerializer
def get_queryset(self,*args,**kwargs):
start_time = self.request.GET.get("start_time",None)
end_time = self.request.GET.get("end_time",None)
if start_time and end_time :
# convert timestamps to timezone objects
start_time_instance = timezone.datetime.fromtimestamp(start_time)
end_time_instance = timezone.datetime.fromtimestamp(end_time)
return self.queryset.filter(start_time=start_time_instance,end_time_instance=end_time_instance)
else:
# do some handling here
return self.queryset
You can customize get_queryset function in the following way:
from django.db.models import Q
class TimespanViewSet(OSKAuthMixin, ModelViewSet):
queryset = Timespan.objects.filter()
serializer_class = TimespanSerializer
def get_queryset(self):
start_timestamp = int(self.request.GET.get("start", "0"))
end_timestamp = int(self.request.GET.get("end", "0"))
if start_timestamp > 0 and start_timestamp > 0:
return Timespan.objects.filter(
Q(start_time___range = (start_timestamp, end_timestamp)) |
Q(end_time___range = (start_timestamp, end_timestamp))
)
else:
return Timespan.objects.all()

Django Queryset - rename original values and re-use original value names

I'm completing a challenge for a job and I'm a little confused with this endpoint's response.
I have the following models:
Attribute
AttributeValue
ProductAttribute
I need to get all attributes that are linked to a given product ID. I have managed to get the values but I can't give them the correct names in the response. The relevant code is in the get_attributes_from_product function:
# src/api/viewsets/attribute.py
from django.db.models import F
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from api import errors
from api.models import Attribute, AttributeValue, ProductAttribute
from api.serializers import AttributeSerializer, AttributeValueSerializer, AttributeValueExtendedSerializer
import logging
logger = logging.getLogger(__name__)
class AttributeViewSet(viewsets.ReadOnlyModelViewSet):
"""
list: Return a list of attributes
retrieve: Return a attribute by ID.
"""
queryset = Attribute.objects.all()
serializer_class = AttributeSerializer
#action(detail=False, url_path='values/<int:attribute_id>')
def get_values_from_attribute(self, request, *args, **kwargs):
"""
Get Values Attribute from Attribute ID
"""
attribute_id = int(kwargs['attribute_id'])
# Filter queryset to find all values for attribute
response = AttributeValue.objects.filter(attribute_id=attribute_id).values(
'attribute_value_id', 'value')
# Return response
if response.exists():
return Response(response, 200)
else:
return Response(response, 204)
#action(detail=False, url_path='inProduct/<int:product_id>')
def get_attributes_from_product(self, request, *args, **kwargs):
"""
Get all Attributes with Product ID
"""
product_id = int(kwargs['product_id'])
# Filter all attributes in product
response = ProductAttribute.objects.filter(product_id=product_id).annotate(
original_attribute_value_id=F('attribute_value_id'),
original_attribute_value=F('attribute_value__value')).values(
attribute_name=F('attribute_value__attribute_id__name'),
attribute_value_id=F('attribute_value_id'),
attribute_value=F('attribute_value__value')
)
# Return response
if response.exists():
return Response(response, 200)
else:
return Response(response, 204)
If I change attribute_value_id=F('attribute_value_id') and attribute_value=F('attribute_value__value') to attribute_value_id1=F('attribute_value_id') and attribute_value1=F('attribute_value__value') the response is successful and all the values are correct, but obviously the key names are wrong.
It should return the following keys: attribute_name, attribute_value_id and attribute_value.
The django ORM will not overwrite existing model attributes with the names of annotated fields.
In order to use names that collide with existing model attributes, you need to use a
serializer class or just format the queryset rows before returning the response.
An example of using a serializer can be found in the django rest-framework
documentation.
Without using a queryset, you can use a list of dict objects in the response. This
is a shortcut though, and using a serializer would probably be better.
class AttributeViewSet(viewsets.ReadOnlyModelViewSet):
# ...
def render_product_attribute_row(self, row):
row["attribute_value_id"] = row.pop("tmp_attribute_value_id")
row["attribute_value"] = row.pop("tmp_attribute_value")
return row
#action(detail=False, url_path='inProduct/<int:product_id>')
def get_attributes_from_product(self, request, *args, **kwargs):
product_id = int(kwargs['product_id'])
queryset = ProductAttribute.objects.filter(product_id=product_id)
queryset = queryset.annotate(
original_attribute_value_id=F('attribute_value_id'),
original_attribute_value=F('attribute_value__value'),
)
queryset = queryset.values(
attribute_name=F('attribute_value__attribute_id__name'),
tmp_attribute_value_id=F('attribute_value_id'),
tmp_attribute_value=F('attribute_value__value'),
)
if queryset.exists():
status_code = 200
else:
status_code = 204
response = [self.render_product_attribute_row(row) for row in queryset]
return Response(response, status_code)

django-rest save array of data to db

hi im trying to save a form data into db.
i provided print of requset.data for you as you see requirement have two items.
i want to save each item in database i used for loop to save each item of list but the loop will save each character of item like h-e-l,... in table row...
where is my mistake ... thanks
also print of request.data.get('requirement') will retun second item
this is print of request.data in sever:
<QueryDict: {'requirement': ['hello', 'bye'], 'audience': ['adasd'], 'achievement': ['asdasd'], 'section': ['410101010'], 'title': ['asdasd'], 'mini_description': ['asdad'], 'full_description': ['asdasd'], 'video_length': ['10101'], 'video_level': ['P'], 'price': [''], 'free': ['true'], 'image': [<InMemoryUploadedFile: p.gif (image/gif)>]}>
view:
class StoreCreateAPIView(generics.CreateAPIView):
parser_classes = (MultiPartParser, FormParser)
permission_classes = [IsAuthenticated]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
def post(self, request, *args, **kwargs):
if request.method == 'POST':
print(request.data)
file_serial = ProductSerializer(data=request.data, context={"request": request})
if file_serial.is_valid():
file_serial.save(author_id=request.user.id)
requirement = request.data['requirement']
audience = request.data.get('audience')
achievement = request.data.get('achievement')
sections = request.data.get('section')
print(request.data['requirement'])
pid = file_serial.data.get('product_id')
for item in requirement :
req = ProductRequiredItems(
item = item,
product_id = pid
)
req.save()
First of all, overriding CreateAPIView's post method in your code makes your custom perform_create method useless, unless you explicitly call it from within your customized post method. Otherwise it will never be called.
also print of request.data.get('requirement') will retun second item
It does return the last item as per Django docs for QueryDict.__getitem__(key).
i want to save each item in database i used for loop to save each item of list but the loop will save each character of item like h-e-l,...
This is because of the above functionality of QueryDict. When you do:
requirement = request.data['requirement']
# requirement = request.__getitem__('requirement')
it will call QueryDict.__getitem__(key) method and thus return only the last item (which is string in you example).
Answer:
You can simply override CreateAPIView's create method, and let your serializer handle all the rest.
# views.py
from django.shortcuts import render
from rest_framework import generics, status
from rest_framework.response import Response
from .models import MyObj
from .serializers import MyObjSerializer
class MyObjView(generics.CreateAPIView):
serializer_class = MyObjSerializer
queryset = MyObj.objects.all()
def create(self, request, *args, **kwargs):
# The QueryDicts at request.POST and request.GET will be immutable
# when accessed in a normal request/response cycle.
# To get a mutable version you need to use QueryDict.copy().
req_data = request.data.copy()
requirements = req_data.pop('requirement')
serializers_data = []
for requirement in requirements:
req_data ['requirement'] = requirement
serializer = self.get_serializer(data=req_data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
serializers_data.append(serializer.data)
return Response(serializers_data, status=status.HTTP_201_CREATED)
# serializers.py
from rest_framework import serializers
from .models import MyObj
class MyObjSerializer(serializers.ModelSerializer):
class Meta:
model = MyObj
fields = '__all__'
Have a look at DRF CreateModelMixin. It defines create & perform_create methods that are used used in CreateAPIView upon executing POST request. I just altered them slightly to handle your specific case.
Hope it helps.

Django Class Based Views - Arbitrary Date Range?

In my Django project I want to display a list of some model (hypothetically, let's use blog posts) and by default, show the list of posts from this month. However, I would also like to be able to show all the posts between an arbitrary date range.
I was looking at the MonthArchiveView which perfectly meets my first want, but I don't think it would for my second.
I was also looking at ArchiveIndexView (which seems similar to ListView?) but I'm not sure if that easily does what I want, either.
Does anyone have any recommendations? Are the built in generic views capable of doing what I'm looking for, or should I go ahead and write my own?
Thanks!
You can use a class that extends from ListView and set the context overriding the parent method for that. Inside that method you can call a function that filters the objects you need.
You can see the example in the documentation.
Also, to get objects with a date in some data range i do something like this:
def get_objects_in_some_range(self):
now = datetime.now()
current = self.filter(end_date__gte=now)
current = current.filter(start_date__lte=now)
return current
You could pass it some arguments to make filter the dates you want.
So the answer here is to create a custom CBV. I've gone ahead and done so, an child of ListView with some inspiration from the DateMixin and BaseDateListView classes. Code will be maintained here but for posterity is included below:
class ArbitraryDateListView(ListView):
date_field = None
date_format = '%Y-%m-%d'
since_date = None
until_date = None
auto_until_date = False
def get_date_field(self):
if self.date_field is None:
raise ImproperlyConfigured("%s.date_field is required." % self.__class__.__name__)
return self.date_field
def get_date_format(self):
"""
Get a date format string in strptime syntax to be used to parse the date from url variables.
"""
return self.date_format
def get_since_date(self):
"""
Return the start date for which this view should display data
"""
since_date = self.since_date
if since_date is None:
try:
since_date = self.kwargs['since']
except KeyError:
try:
since_date = self.request.GET['since']
except KeyError:
raise Http404("No since date specified")
format = self.get_date_format()
return datetime.datetime.strptime(since_date, format).date()
def get_until_date(self):
"""
Return the end date for which this view should display data
"""
until_date = self.until_date
if until_date is None:
try:
until_date = self.kwargs['until']
except KeyError:
try:
until_date = self.request.GET['until']
except KeyError:
if self.auto_until_date:
return self.get_until_date_from_since_date()
else:
raise Http404("No until date specified")
format = self.get_date_format()
return datetime.datetime.strptime(until_date, format).date()
def get_until_date_from_since_date(self):
"""
Return a calculated end date from start date when end date is not provided
"""
if self.auto_until_date:
raise NotImplementedError("If auto_util_date=True, get_until_date_from_since_date must be implemented")
def get_queryset(self):
queryset = super(ArbitraryDateListView, self).get_queryset()
date_field = self.get_date_field()
since = self.get_since_date()
until = self.get_until_date()
lookup_kwargs = {
'%s__gte' % date_field: since,
'%s__lt' % date_field: until,
}
queryset = queryset.filter(**lookup_kwargs).order_by('-%s' % date_field)
return queryset
def get_context_data(self, **kwargs):
context = {'since': self.get_since_date(),
'until': self.get_until_date()}
context.update(kwargs)
return super(ArbitraryDateListView, self).get_context_data(**context)

Django custom widget to split an IntegerField into hours and seconds fields in a Model Form

I am storing time duration as minutes in an IntegerField. In my front end form (Model Form), I want to split the input into one field for minutes and one field for hours. I also want to be able to use the InLineFormset. Can this be done in a nice way? I could use javascript, but that does not seeml like a good solution to me, or is it?
You want to use a MultiWidget. I'd suggest that you override the field itself. Have a look at the docs for creating custom fields.
If you can, it might be worth storing your data as a floating point representing a python timedelta. You may want to add a few custom methods to your field. Using timedelta will be useful if you want to do any calculations with your field.
You'll probably want to do a bit of reading on form validation too.
I've tested the following hours, mins, sec multiwidget with float based custom field on Django 1.4 -
from datetime import timedelta
from django import forms
from django.db import models
from django.core import exceptions, validators
def validate_timedelta(value):
if not isinstance(value, timedelta):
raise exceptions.ValidationError('A valid time period is required')
class SplitHoursMinsWidget(forms.widgets.MultiWidget):
def __init__(self, *args, **kwargs):
widgets = (
forms.TextInput(),
forms.TextInput(),
forms.TextInput(),
)
super(SplitHoursMinsWidget, self).__init__(widgets, *args, **kwargs)
def decompress(self, value):
if value:
return [value.seconds // 3600, (value.seconds // 60) % 60,
value.seconds % 60]
return [None, None, None]
def format_output(self, rendered_widgets):
return ("HH:MM:SS " + rendered_widgets[0] + ":" + rendered_widgets[1] +
":" + rendered_widgets[2])
def value_from_datadict(self, data, files, name):
hours_mins_secs = [
widget.value_from_datadict(data, files, name + '_%s' % i)
for i, widget in enumerate(self.widgets)]
try:
time_delta = (timedelta(hours=float(hours_mins_secs[0])) +
timedelta(minutes=float(hours_mins_secs[1])) +
timedelta(seconds=float(hours_mins_secs[2])))
except ValueError:
return None
else:
return time_delta
class TimeDeltaFormField(forms.Field):
widget = SplitHoursMinsWidget
class TimeDeltaField(models.Field):
__metaclass__ = models.SubfieldBase
description = "Field to hold a python timedelta object"
default_validators = [validate_timedelta, ]
def to_python(self, value):
if value in validators.EMPTY_VALUES or isinstance(value, timedelta):
return value
try:
return timedelta(seconds=float(value))
except:
raise exceptions.ValidationError('A valid time period is required')
def get_prep_value(self, value):
return float(value.days * 86400 + value.seconds)
def get_internal_type(self):
return 'FloatField'
def formfield(self, **kwargs):
defaults = {'form_class': TimeDeltaFormField}
defaults.update(kwargs)
return super(TimeDeltaField, self).formfield(**defaults)
It's worth noting that this doesn't provide any useful input level validation (it's possible to enter over 60 in both the minutes and seconds input elements). Perhaps that could be best resolved with javascript.