Remove Z from DateTimeField in serializer - django

Is there a way to make my serializer print the datetimefield by default like this
2022-03-28T00:00:00+00:00
Instead of this
2022-03-23T03:16:00Z
I get the first output when I do this
return obj.time.isoformat()

Cause
If you look into the code of django-rest-framework in serializers.DateTimeField if datetime is UTC timezone, the UTC offset (+00:00) will get converted to Z as can be seen here
Solution
If you want to make it reusable for DateTimeField, you need to create a custom serializer DateTimeField that inherits from serializers.DateTimeField and override the to_representation method by coping codes from django-rest-framework and removing lines that convert UTC offset string to Z.
from restframework import ISO_8601
from restframework import serializers
class CustomDateTimeField(serializers.DateTimeField):
def to_representation(self, value):
if not value:
return None
output_format = getattr(self, 'format', api_settings.DATETIME_FORMAT)
if output_format is None or isinstance(value, str):
return value
value = self.enforce_timezone(value)
if output_format.lower() == ISO_8601:
value = value.isoformat()
# remove lines that convert "+00:00" to "Z"
# See https://github.com/encode/django-rest-framework/blob/f4cf0260bf3c9323e798325702be690ca25949ca/rest_framework/fields.py#L1239:L1240
return value
return value.strftime(output_format)
Then use this in your serializer instead of serializers.DateTimeField
class MySerializer(serializers.Serializer):
datetime = CustomDateTimeField()
Extra
If you want to use it in serializers.ModelSerializer, you need to follow below steps
Create a custom ModelSerializer that inherits from serializers.ModelSerializer and set serializer_field_mapping attribute as follows
class CustomModelSerializer(serializers.ModelSerializer):
serializer_field_mapping = {
# copy serializer_field_mapping
**serializers.ModelSerializer.serializer_field_mapping,
# override models.DateTimeField to map to CustomDateTimeField
models.DateTimeField: CustomDateTimeField,
}
Use CustomModelSerializer instead of serializers.ModelSerializer. E.g.
class LogSerializer(CustomModelSerializer):
class Meta:
model = Log
fields = ["id", "created_at"]

The simplest way to do it is to specify the format you want in your serializer:
class MySerializer(serializer.Serializer):
my_date = serializers.DateTimeField(format='%Y-%m-%dT%H:%M') # Specify your format here

Related

Use non model django fields in django filter

I have a variable 'cptCodeTBX' which is not present as fields in django models. I need to apply filter on 'cptCodeTBX' variable. Something roughly equivalent to
cptCodeTBX = '00622'
select * from cpt where cpt.code like cptCodeTBX or cptCodeTBX is != ''
In dot net entity framework we could do it by
b = cptContext.CPTs.AsNoTracking().Where(
a =>
(String.IsNullOrEmpty(cptCodeTBX) || a.Code.StartsWith(cptCodeTBX))
This may not be the most performant solution, but I was able to get it working.
Step 1: Read the Django Filter docs.
https://django-filter.readthedocs.io/en/stable/index.html
Step 2: Add a property to your Django model named cptCodeTBX.
from django.db import models
class MyModel(models.Model):
field = models.CharField(max_length=60)
#property
def cptCodeTBX(self):
"""
Does all the code tbx cpt stuff, can do whatever you want.
"""
cptCodeTBX = 2323 #whatever value you want
return cptCodeTBX
Step 3: Add a Django Filter.
import django_filters
class CptCodeTBXFilter(django_filters.FilterSet):
"""
Filter that will do all the magic, value comes from url query params.
Replace filters.NumberFilter with any filter you want like
filters.RangeFilter.
"""
cptCodeTBX = django_filters.NumberFilter(
field_name="cptCodeTBX",
method="filter_cptCodeTBX",
label="Cpt Code TBX",
)
def filter_cptCodeTBX(self, queryset, name, value):
objects_ids = [
obj.pk for obj in MyModel.objects.all() if obj.cptCodeTBX == value
]
if objects_ids:
queryset = MyModel.objects.filter(pk__in=objects_ids)
else:
queryset = MyModel.objects.none()
return queryset
Step 4: Pass the value through the url as a query parameter.
http://example.com/?cptCodeTBX=00622

Converting string into datetime within Django modelform

I would like to accept natural language strings into a DateTime field, on a form generated by a Django ModelForm. I found Converting string into datetime, which explains how to convert the input string into a DateTime object. (In this case, I'm using timestring for the conversion, instead of strptime as suggested in the answers, because I want to handle input like 'tomorrow'.) But I can't figure out where code this like should be placed within the ModelForm code. If the conversion code is placed in form_valid, it never gets run, because is_python runs first and complains that the text input is not a DateTime. When I override is_python, I get an error that I think comes from some kind of recursive loop.
Relevant code:
models.py
class Widget(models.Model):
name = models.CharField(max_length=100)
widget_date = models.DateTimeField
forms.py
from timestring import Date
class NaturalDateField(forms.DateField):
def to_python(self, value):
if not value:
return none
return Date(value, tz=timezone.get_current_timezone())
class WidgetForm(forms.ModelForm):
widget_date = NaturalDateField()
class Meta:
model = Widget
fields = ['name', 'widget_date']
views.py
class WidgetUpdate(UpdateView):
model = Widget
form_class = WidgetForm
The error on submit is Invlid date string >>. Tracing the code shows that the initial input string converts correctly (to something like '2014-12-26 00:00:00-08:00'), but then the validate() function from site-packages/django/forms/fields.py runs and that goes back into the timestring package for some reason and tries to run def __eq__(self, other): from Date.py, which I think tries to run Date(other), which fails since other is blank.
What is the best method to accept a text string in a ModelForm and then convert it to a field-specific string such as DateTime to be saved in the database?
Looking at that project, your code will return a timestring.Date object, which Django doesn't know what to do with. You probably just need to get the date value from there:
def to_python(self, value):
if not value:
return none
parsed_date = Date(value, tz=timezone.get_current_timezone())
return parsed_date.date
Try reusing builtin django date parsing methods:
class NaturalDateField(forms.DateField):
def to_python(self, value):
value = super(NaturalDateField, self).to_python(value)
return value.replace(tzinfo=timezone.get_current_timezone())

How to I change the rendering of a specific field type in Django admin?

For example I have an IntegerField and I want to change how it is displayed all across Django admin.
I considered subclassing it and overriding __str__ and __unicode__ methods but it doesn't seam to work.
class Duration(models.IntegerField):
def __unicode__(self):
return "x" + str(datetime.timedelta(0, self))
def __str__(self):
return "y" + str(datetime.timedelta(0, self))
Update: I just want to chage the way the field is displayed, not the edit control (widget).
I'm not sure what you want to do with the field, but if you want to change the HTML that is displayed, you need to either change the widget that the form field is using, or create your own custom widget:
https://docs.djangoproject.com/en/dev/ref/forms/widgets/
models.py
class LovelyModel(models.Model):
my_int = models.IntegerField()
forms.py
from widgets import WhateverWidgetIWant
class LovelyModelForm(forms.ModelForm):
my_int = models.IntegerField(widget=WhateverWidgetIWant())
class Meta:
model = LovelyModel
admin.py
from forms import LovelyModelForm
class LovelyModelAdmin(admin.ModelAdmin):
form = LovelyModelForm
What is it you are trying to do?
I think you need something like this (untested code)::
import datetime
from django.db import models
class Duration(models.IntegerField):
description = "Stores the number of seconds as integer, displays as time"
def to_python(self, value):
# this method can receive the value right out of the db, or an instance
if isinstance(value, models.IntegerField):
# if an instance, return the instance
return value
else:
# otherwise, return our fancy time representation
# assuming we have a number of seconds in the db
return "x" + str(datetime.timedelta(0, value))
def get_db_prep_value(self, value):
# this method catches value right before sending to db
# split the string, skipping first character
hours, minutes, seconds = map(int, value[1:].split(':'))
delta = datetime.timedelta(hours=hours, minutes=minutes, seconds=seconds)
return delta.seconds
This, however, changes how the field's value represented in Python at all, not only in admin, which may not be a desired behaviour. I.e., you have object.duration == 'x00:1:12', which would be saved to the database as 72.
See also documentation on custom fields.

Is it possible to specify a QuerySet model dynamically as a string?

I am trying to build a query in Django dynamically. I have a lot of models that I would like to build a query for, but I don't want to code the name of the model, I want to pass it as a string.
from django.db.models.query import QuerySet
a_works = QuerySet(model_A)
a_doesnt_work = QuerySet("model_A") # I want this to work, too
a_works.filter(pk=23) # no error
a_doesnt_work.filter(pk=23) # error: AttributeError: 'str' object has no attribute '_meta'
# then I am dynamically filtering different fields, which works fine with a_works
kwargs = { "%s__%s" % (field, oper) : val }
results = a_works.filter( **kwargs )
Is there a way to make the dynamic model selection work?
Don't try and build querysets via the QuerySet class itself. You should always go via a model's Manager.
You can get the model via the get_model function defined in django.db.models. It takes parameters of the app name and the model name.
from django.db.models import get_model
model = get_model('myapp', 'modelA')
model.objects.filter(**kwargs)
Refer this: https://stackoverflow.com/a/75168880/7212249
from django.apps import apps
def get_app_label_and_model_name(instance: object):
"""
get_model(), which takes two pieces of information — an “app label” and “model name” — and returns the model
which matches them.
#return: None / Model
"""
app_label = instance._meta.app_label
model_name = instance.__class__.__name__
model = apps.get_model(app_label, model_name)
return model
How to use?
model_name = get_app_label_and_model_name(pass_model_object_here)
and use this to get dynamic model name for queries
model_name = get_app_label_and_model_name(pass_model_object_here)
query_set = model_name.objects.filter() # or anything else

Django: Get list of model fields?

I've defined a User class which (ultimately) inherits from models.Model. I want to get a list of all the fields defined for this model. For example, phone_number = CharField(max_length=20). Basically, I want to retrieve anything that inherits from the Field class.
I thought I'd be able to retrieve these by taking advantage of inspect.getmembers(model), but the list it returns doesn't contain any of these fields. It looks like Django has already gotten a hold of the class and added all its magic attributes and stripped out what's actually been defined. So... how can I get these fields? They probably have a function for retrieving them for their own internal purposes?
Django versions 1.8 and later:
You should use get_fields():
[f.name for f in MyModel._meta.get_fields()]
The get_all_field_names() method is deprecated starting from Django
1.8 and will be removed in 1.10.
The documentation page linked above provides a fully backwards-compatible implementation of get_all_field_names(), but for most purposes the previous example should work just fine.
Django versions before 1.8:
model._meta.get_all_field_names()
That should do the trick.
That requires an actual model instance. If all you have is a subclass of django.db.models.Model, then you should call myproject.myapp.models.MyModel._meta.get_all_field_names()
As most of answers are outdated I'll try to update you on Django 2.2
Here posts- your app (posts, blog, shop, etc.)
1) From model link: https://docs.djangoproject.com/en/stable/ref/models/meta/
from posts.model import BlogPost
all_fields = BlogPost._meta.fields
#or
all_fields = BlogPost._meta.get_fields()
Note that:
all_fields=BlogPost._meta.get_fields()
Will also get some relationships, which, for ex: you can not display in a view.
As in my case:
Organisation._meta.fields
(<django.db.models.fields.AutoField: id>, <django.db.models.fields.DateField: created>...
and
Organisation._meta.get_fields()
(<ManyToOneRel: crm.activity>, <django.db.models.fields.AutoField: id>, <django.db.models.fields.DateField: created>...
2) From instance
from posts.model import BlogPost
bp = BlogPost()
all_fields = bp._meta.fields
3) From parent model
Let's suppose that we have Post as the parent model and you want to see all the fields in a list, and have the parent fields to be read-only in Edit mode.
from django.contrib import admin
from posts.model import BlogPost
#admin.register(BlogPost)
class BlogPost(admin.ModelAdmin):
all_fields = [f.name for f in Organisation._meta.fields]
parent_fields = BlogPost.get_deferred_fields(BlogPost)
list_display = all_fields
read_only = parent_fields
The get_all_related_fields() method mentioned herein has been deprecated in 1.8. From now on it's get_fields().
>> from django.contrib.auth.models import User
>> User._meta.get_fields()
I find adding this to django models quite helpful:
def __iter__(self):
for field_name in self._meta.get_all_field_names():
value = getattr(self, field_name, None)
yield (field_name, value)
This lets you do:
for field, val in object:
print field, val
This does the trick. I only test it in Django 1.7.
your_fields = YourModel._meta.local_fields
your_field_names = [f.name for f in your_fields]
Model._meta.local_fields does not contain many-to-many fields. You should get them using Model._meta.local_many_to_many.
It is not clear whether you have an instance of the class or the class itself and trying to retrieve the fields, but either way, consider the following code
Using an instance
instance = User.objects.get(username="foo")
instance.__dict__ # returns a dictionary with all fields and their values
instance.__dict__.keys() # returns a dictionary with all fields
list(instance.__dict__.keys()) # returns list with all fields
Using a class
User._meta.__dict__.get("fields") # returns the fields
# to get the field names consider looping over the fields and calling __str__()
for field in User._meta.__dict__.get("fields"):
field.__str__() # e.g. 'auth.User.id'
def __iter__(self):
field_names = [f.name for f in self._meta.fields]
for field_name in field_names:
value = getattr(self, field_name, None)
yield (field_name, value)
This worked for me in django==1.11.8
A detail not mentioned by others:
[f.name for f in MyModel._meta.get_fields()]
get, for example
['id', 'name', 'occupation']
and
[f.get_attname() for f in MyModel._meta.get_fields()]
get
['id', 'name', 'occupation_id']
If
reg = MyModel.objects.first()
then
reg.occupation
get, for example
<Occupation: Dev>
and
reg.occupation_id
get
1
MyModel._meta.get_all_field_names() was deprecated several versions back and removed in Django 1.10.
Here's the backwards-compatible suggestion from the docs:
from itertools import chain
list(set(chain.from_iterable(
(field.name, field.attname) if hasattr(field, 'attname') else (field.name,)
for field in MyModel._meta.get_fields()
# For complete backwards compatibility, you may want to exclude
# GenericForeignKey from the results.
if not (field.many_to_one and field.related_model is None)
)))
Just to add, I am using self object, this worked for me:
[f.name for f in self.model._meta.get_fields()]
At least with Django 1.9.9 -- the version I'm currently using --, note that .get_fields() actually also "considers" any foreign model as a field, which may be problematic. Say you have:
class Parent(models.Model):
id = UUIDField(primary_key=True)
class Child(models.Model):
parent = models.ForeignKey(Parent)
It follows that
>>> map(lambda field:field.name, Parent._model._meta.get_fields())
['id', 'child']
while, as shown by #Rockallite
>>> map(lambda field:field.name, Parent._model._meta.local_fields)
['id']
So before I found this post, I successfully found this to work.
Model._meta.fields
It works equally as
Model._meta.get_fields()
I'm not sure what the difference is in the results, if there is one. I ran this loop and got the same output.
for field in Model._meta.fields:
print(field.name)
In sometimes we need the db columns as well:
def get_db_field_names(instance):
your_fields = instance._meta.local_fields
db_field_names=[f.name+'_id' if f.related_model is not None else f.name for f in your_fields]
model_field_names = [f.name for f in your_fields]
return db_field_names,model_field_names
Call the method to get the fields:
db_field_names,model_field_names=get_db_field_names(Mymodel)
Combined multiple answers of the given thread (thanks!) and came up with the following generic solution:
class ReadOnlyBaseModelAdmin(ModelAdmin):
def has_add_permission(self, request):
return request.user.is_superuser
def has_delete_permission(self, request, obj=None):
return request.user.is_superuser
def get_readonly_fields(self, request, obj=None):
return [f.name for f in self.model._meta.get_fields()]
Why not just use that:
manage.py inspectdb
Example output:
class GuardianUserobjectpermission(models.Model):
id = models.IntegerField(primary_key=True) # AutoField?
object_pk = models.CharField(max_length=255)
content_type = models.ForeignKey(DjangoContentType, models.DO_NOTHING)
permission = models.ForeignKey(AuthPermission, models.DO_NOTHING)
user = models.ForeignKey(CustomUsers, models.DO_NOTHING)
class Meta:
managed = False
db_table = 'guardian_userobjectpermission'
unique_together = (('user', 'permission', 'object_pk'),)