I am building a project in which I need to have a few custom settings that are editable by certain users which do not have access to the django admin panel, who access the application via an Angular frontend.
I installed django-constance in order to have these custom settings editable, as it was the most recommended package I could find on the Django packages site. My problem now is that I do not know how to do to serve the settings on my API rest.
I started with some customized view:
class ConstanceSettingSerializer(serializers.Serializer):
name = serializers.CharField()
value = serializers.Field()
defult_value = serializers.Field()
modified = serializers.BooleanField(read_only=True)
from constance import settings as constance_settings
class ConstanceView(APIView):
def get(self, request):
settings = constance_settings.CONFIG.items()
return Response(settings, status=status.HTTP_200_OK)
But now I dont know how to pass those settings correctly to my serializer. I tried this without success:
settings_serializer = ConstanceSettingSerializer(settings)
return Response(settings_serializer.data, status=status.HTTP_200_OK)
And I am only getting errors.
Some idea how to do this properly?
Thanks!
Give you a demo
from .utils import get_settings
from constance import config
from rest_framework.viewsets import ViewSet
class SettingViewSet(ViewSet):
permission_classes = (IsAuthenticated,)
def setting(self, request, allow_settings):
if request.method == 'GET':
# list all setting items
return Response(data=get_settings(allow_settings))
else:
# change all allow setting items in allow_settings
for key in request.data:
if key in allow_settings and key in getattr(settings, 'CONSTANCE_CONFIG', {}):
value = request.data[key]
setattr(config, key, '' if value is None else value)
return Response(data=get_settings(allow_settings))
def create(self, request):
"""
<p>update with POST:<code>{'Key': new_value}</code>
"""
allow_settings = [key for key, options in getattr(settings, 'CONSTANCE_CONFIG', {}).items()]
return self.setting(request, allow_settings)
def list(self, request):
"""
get all setting item
"""
allow_settings = [key for key, options in getattr(settings, 'CONSTANCE_CONFIG', {}).items()]
return self.setting(request, allow_settings)
in utils.py:
def get_settings(allow_settings):
setting_list = []
for key, options in getattr(settings, 'CONSTANCE_CONFIG', {}).items():
if key in allow_settings:
default, help_text = options[0], options[1]
data = {'key': key,
'default': default,
'help_text': help_text,
'value': getattr(config, key)}
setting_list.append(data)
return setting_list
in urls.py:
router.register(r'setting', SettingViewSet, base_name='setting')
ask get http://x.x.x.x/setting/ you can get:
[
{
"key": "staff_prefix",
"default": "",
"help_text": "工号固定代码",
"value": ""
},
{
"key": "staff_suffix",
"default": 3,
"help_text": "工号尾缀",
"value": "3"
}
]
post http://x.x.x.x/setting/ with data {'staff_prefix': 'demo'} you can update value in staff_prefix.
Your serializer needs to know what to return. So for each field, you can do something like this:
class ConstanceSettingSerializer(serializers.Serializer):
setting_name = serializers.SerializerMethodField()
def get_setting_name(self):
settings = constance_settings.CONFIG.items()
# Retrieve your setting's value here and return
return setting_value
That's on the way out. If you wanted to be able to change settings via PUT/POST/PATCH, you need to override create/update in your serializer and implement the logic to save the values yourself.
Another way could be to check how the storage of the settings is implemented.
I have not used the constance settings so i cant say. But if it uses DB to store the settings, you can implement model serializer for the model which they use.
You can also do the same with a serializer if you using database backend:
serializer.py:
from constance.backends.database.models import Constance
from constance import config
from django.conf import settings
from rest_framework import serializers
class ConfigSerializer(serializers.ModelSerializer):
default = serializers.SerializerMethodField()
help_text = serializers.SerializerMethodField()
value = serializers.SerializerMethodField()
class Meta:
model = Constance
fields = ('key', 'default', 'help_text', 'value')
def get_default(self, obj):
default, _ = settings.CONSTANCE_CONFIG.get(obj.key)
return default
def get_help_text(self, obj):
_, help_text = settings.CONSTANCE_CONFIG.get(obj.key)
return help_text
def get_value(self, obj):
return getattr(config, obj.key, None)
views.py:
from rest_framework.viewsets import ReadOnlyModelViewSet
from rest_framework.permissions import IsAuthenticated
from constance.backends.database.models import Constance
from myproject.apps.common.api.v1.serializers import ConfigSerializer
class ConfigAPIView(ReadOnlyModelViewSet):
serializer_class = ConfigSerializer
permission_classes = [IsAuthenticated]
queryset = Constance
urls.py:
from django.urls import path
from myproject.apps.common.api.v1.views import ConfigAPIView
urlpatterns = [
path(
"config/",
ConfigAPIView.as_view({"get": "list"}),
name="config-list",
),
]
Related
I'm trying the RetrieveUpdateDeleteAPIView and my update method gives me a key error every time I update a course object
THE UPDATE METHOD WORKS
api_views.py
from rest_framework.generics import (
RetrieveUpdateDestroyAPIView
)
from .serializers import CourseSerializer
from .models import Course
from django.core import cache
class CourseRetrieveUpdateDestroy(RetrieveUpdateDestroyAPIView):
queryset = Course.objects.all()
lookup_field = 'id'
serializer_class = CourseSerializer
def delete(self, request, *args, **kwargs):
course_id = request.data.get('id')
response = super().delete(request, *args, **kwargs)
if response.status_code == 204:
cache.delete('course_data_{}'.format(course_id))
return response
def update(self, request, *args, **kwargs):
response = super().update(request, *args, **kwargs)
if response.status_code == 200:
from django.core.cache import cache
course = response.data
cache.set('course_data_{}'.format(course['id']), {
'title' : course['title'],
'description': course['description'],
'featured': course['featured'],
})
return response
serializers.py
from rest_framework import serializers
from .models import Course
class CourseSerializer(serializers.ModelSerializer):
class Meta:
model = Course
fields = ('title', 'description', 'issued_at', )
def to_representation(self, instance):
data = super().to_representation(instance)
data['time_passed'] = instance.time_passed()
data['is_fresh'] = instance.is_fresh
return data
urls.py
from django.urls import include, path
from django.conf import settings
from django.conf.urls.static import static
from .views import (
CourseListView,
CourseDetailView
)
from .api_views import (
#CourseList,
#CourseCreate,
CourseRetrieveUpdateDestroy
)
app_name = 'courses'
urlpatterns = [
path('', CourseListView.as_view(), name="course_list"),
path('course/<int:pk>/', CourseDetailView.as_view(), name="course_detail"),
#api-views
path('api/course/<int:id>/', CourseRetrieveUpdateDestroy.as_view(), name="course_rud_api"),
#path('api/list/', CourseList.as_view(), name="course_list_api"),
#path('api/create/', CourseCreate.as_view(), name="course_create_api"),
]
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
**Whenever I try to update an object of given id and hit enter, a KeyError at /courses/api/course/2/ is returned (id=2 as an example)
'id' being the key.
Why does it give me a key error for fields that I have not requested to serialize in the serializers.py? The model updates even after it redirects me to the error page.
How do i resolve this error or avoid this error page?
You didnt include the id field in your serializer fields, so it will not be in the response data. Try this:
class CourseSerializer(serializers.ModelSerializer):
class Meta:
model = Course
fields = ('id', 'title', 'description', 'issued_at', )
id will by default be a read-only field. So the user cannot override it when sending data, but it will be in the sent response data.
I am creating my first Django project . I have taken 30,000 values as input and want to show particular values according to primary key .
Code:
class employeesList(APIView):
def get(self,request):
employees1 = employees.objects.all()
with open('tracking_ids.csv') as f:
reader = csv.reader(f)
for row in reader:
_, created = employees.objects.get_or_create(
AWB=row[0],
Pickup_Pincode=row[1],
Drop_Pincode=row[2],
)
serializer = employeesSerializer(employees1 , many=True)
return Response(serializer.data)
def post(self,request):
# employees1 = employees.objects.get(id=request.employees.AWB)
employees1 = employees.objects.all()
serializer = employeesSerializer(employees1 , many=True)
return Response(serializer.data)
If I enter http://127.0.0.1:8000/employees/ in URL , I get all the values . I want the URL to be like http://127.0.0.1:8000/employees/P01001168074 and show values of P01001168074 where P01001168074 is primary ID .
I have read
1:showing the model values of specific user django
2)editing user details in python django rest framework
but they are different
Can it be done and if it can , then how ?
Presuming that you are using Django 2.0 you must configure a path than can capture your parameter as seen in the documentation
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer
class A(models.Model):
x = models.CharField(max_length=250)
class MySerializer(ModelSerializer):
class Meta:
model = A
fields = ('x',)
class MyListView(APIView):
def get(self, request, *args, **kwargs):
# simply get all the objects, serialize and return the response
elems = A.objects.all()
response_data = MySerializer(elems, many=True).data
return Response(data=response_data)
class MyDetailView(APIView):
def get(self, request, *args, **kwargs):
# we obtain the parameter from the URL
desired_item = kwargs.get("desired_item", None)
# we filter the objects by it and get our instance
element = A.objects.filter(x=desired_item).first()
# serialize the instance and return the response
response_data = MySerializer(element).data
return Response(data=response_data)
# all we have to now is define the paths for the list and the detail views.
urlpatterns = [
path('employees/', MyListView.as_view()),
path('employees/<str:desired_item>', MyDetailView.as_view())
]
One good option is to use a viewset that already includes a list and detail endpoint and requires little to code for a default simple setup
views.py
from rest_framework import viewsets
class EmployeeViewSet(viewsets.ModelViewSet):
serializer_class = EmployeeSerializer
queryset = Employee.objects.all()
urls.py
from rest_framework.routers import SimpleRouter
from views import EmployeeViewSet
router = SimpleRouter()
router.register(r'employees', EmployeeViewSet, base_name='employees')
urlpatterns = router.get_urls()
You can read more about viewsets in the DRF docs
First time developing a django application, and am trying to do something somewhat non-standard...
Is there a way to configure a view that will allow a user to look up a certain model by either one of two unique model attributes.
Ideally, both of these URL schemes would be possible
urlpatterns = [
path('api/somemodel/<int:model_id>/', views.SomeModelDetailView.as_view())
path('api/somemodel/<str:model_name>/', views.SomeModelDetailView.as_view())
]
A simplified example model... Both the id and the name are guaranteed to be unique. Also, by convention, my data is entered in such a way that a name will always be a string and never an integer
from django.db import models
class SomeModel(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100, unique=True)
Currently, I have this working with the following view...
from rest_framework import generics
from rest_framework import status
from rest_framework.response import Response
from . import models
class SomeModelDetailView(generics.RetrieveAPIView):
queryset = models.SomeModel.objects.all()
serializer_class = serializers.SomeModelSerializer
def get(self, request, model_name=None, model_id=None, format=None):
field = None
key = None
try:
if model_id:
field = "model_id"
key = model_id
m = models.SomeModel.objects.get(id=model_id)
elif model_name:
field = "model_name"
key = model_name
m = models.SomeModel.objects.get(name=model_name)
else:
return Response("Neither model_id nor model_name were provided", status=status.HTTP_400_BAD_REQUEST)
except models.SomeModel.DoesNotExist:
return Response("Unknown {field}: {key}".format(field=field, key=key), status=status.HTTP_400_BAD_REQUEST)
serializer_class = self.get_serializer_class()
serializer = serializer_class(m)
return Response(serializer.data)
However, I am wondering if there is a better way that fits more into a ViewSet/Router (or other) DRF mechanic.
Any ideas?
I think both existing answers (Don's and changak's) are very informative... however I wanted to take it a step further.
This is what I ended up with - it is inspired from Changak's answer however is slightly more generic
class MultiKeyGetObject(generics.GenericAPIView):
def __init__(self):
if not hasattr(self, 'lookup_fields'):
raise AssertionError("Expected view {} to have `.lookup_fields` attribute".format(self.__class__.__name__))
def get_object(self):
for field in self.lookup_fields:
if field in self.kwargs:
self.lookup_field = field
break
else:
raise AssertionError(
'Expected view %s to be called with one of the lookup_fields: %s' %
(self.__class__.__name__, self.lookup_fields))
return super().get_object()
I also loved learning about Q objects from Don - I can imagine a use case where you would want to retrieve objects using ALL of the lookup fields (either an AND or an OR). I feel this is getting into filter territory, however it may be useful...
from functools import reduce
from operator import or_
from rest_framework.generics import get_object_or_404
def get_object(self):
query = reduce(or_, [Q(**{field: self.kwargs[field]}) for field in self.lookup_fields if field in self.kwargs])
obj = get_object_or_404(self.get_queryset(), query)
self.check_object_permissions(self.request, obj)
return obj
Both of the above methods can then be used by a view such as...
class SomeObjectDetailAPIView(MultiKeyGetObject, generics.RetrieveUpdateDestroyAPIView):
serializer_class = serializers.SomeModelSerializer
queryset = models.SomeModel.objects.all()
lookup_fields = ('id', 'name')
I believe you can do that in a generic way, first change the urlpttern to this:
urlpatterns = [
path('api/somemodel/<str:pk>/', views.SomeModelDetailView.as_view()),
] ### this path matches both of the keys you wanted str and integer(integer is a str too)
then in the view you only need to override the get_object() in this way(don't override get() that is not the drf way):
from rest_framework import generics
from rest_framework import status
from rest_framework.response import Response
from . import models
class SomeModelDetailView(generics.RetrieveAPIView):
queryset = models.SomeModel.objects.all()
serializer_class = serializers.SomeModelSerializer
lookup_url_kwarg = 'pk'
def get_object(self):
pk = self.kwargs[self.lookup_url_kwarg] ## first get value the url parameter(pk)
### then here convert its type to int if it's an integer,
### it's not a bad thing, path() will have done this if we specify its type `int` in the url
try:
self.kwargs[self.lookup_url_kwarg] = int(pk)
self.lookup_field = 'id' ### change the lookup field to 'id' if it's an integer
except:
self.lookup_field = 'name' ### change the lookup field to 'name' if it's a str
return super(SomeModelDetailView, self).get_object() ## finally call the super get_object
This is possible using what Django calls "Q objects". These allow you to perform logical operations to queries allowing you to query for id=model_id or name=model_name.
For example:
from django.db.models import Q
...
m = models.SomeModel.objects.get(Q(id=model_id) | Q(name=model_name))
...
I've created an AssetsFilter class:
from django_filters import Filter
from django_filters import rest_framework as filters
from django_filters.fields import Lookup
from .models import Assets
class MyListFilter(Filter):
def filter(self, qs, value):
value_list = value.split(',')
return super(ListFilter, self).filter(qs, Lookup(value_list, 'in'))
class AssetsFilter(filters.FilterSet):
name = filters.CharFilter(lookup_expr='icontains', help_text=u'Filter by name')
criticality = MyListFilter(name='critical', help_text=u'Filter by_id')
class Meta:
model = Assets
fields = ['name', 'criticality ']
Now I'm using this filter in my Viewset as follows:
from .serializers import AssetSerializers
from .filters import AssetsFilter
class AssetViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
"""
queryset = Assets.objects.all()
serializer_class = AssetSerializers
filter_backends = (DjangoFilterBackend,)
filter_class = AssetsFilter
http_method_names = ['get', 'post', 'put', 'delete']
def list(self, request):
"""
Returns a list of Asset.
"""
return super(AssetViewSet, self).list(request)
def create(self, request):
"""
Creates a new Asset.<br>
"""
return super(AssetViewSet, self).create(request)
def destroy(self, request, pk=None):
"""
Deletes a Asset.
"""
return super(AssetViewSet, self).destroy(request, pk=pk)
def retrieve(self, request, pk=None):
"""
Returns a Asset with id={id}
"""
return super(AssetViewSet, self).retrieve(request, pk=pk)
def update(self, request, pk=None, *args, **kwargs):
"""
Updates an existing Asset.<br>
"""
return super(AssetViewSet, self).update(request, pk=pk, *args, **kwargs)
When the swagger documentation is created, the filter fields appear in GET (list), GET (retrieve) as expected, but they also appear in the POST, PUT, PATCH and DELETE where they shouldn't be.
How can I disable those parameters from appearing in the latest version of django-rest-swagger and DRF?
If you don't want to Add Schema Manually(Every time) then Here is the solution:
auto_schema.py
from rest_framework.schemas import AutoSchema
from django.utils.six.moves.urllib import parse as urlparse
import coreapi, coreschema
class CustomSchema(AutoSchema):
def get_link(self, path, method, base_url):
fields = self.get_path_fields(path, method)
fields += self.get_serializer_fields(path, method)
fields += self.get_pagination_fields(path, method)
if self.view.action in ['list']:
fields += self.get_filter_fields(path, method)
manual_fields = self.get_manual_fields(path, method)
fields = self.update_fields(fields, manual_fields)
if fields and any([field.location in ('form', 'body') for field in fields]):
encoding = self.get_encoding(path, method)
else:
encoding = None
description = self.get_description(path, method)
if base_url and path.startswith('/'):
path = path[1:]
return coreapi.Link(
url=urlparse.urljoin(base_url, path),
action=method.lower(),
encoding=encoding,
fields=fields,
description=description
)
views.py
class MyUserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
model = MyUser
serializer_class = MyUserSerializer
queryset = MyUser.objects.all()
filter_backends = (DjangoFilterBackend, OrderingFilter)
filter_class = MyUserFilter
ordering_fields = ('last_name', 'first_name', 'email', 'is_active')
ordering = ('last_name', 'first_name')
permission_classes = (IsAuthenticated,)
schema = CustomSchema()
Specifying a CustomSchema for every model is not necessary.
It is possible to override the DjangoFilterBackend and prevent it from adding the schema fields for some specified actions:
class MyDjangoFilterBackend(DjangoFilterBackend):
def get_schema_fields(self, view):
# Customize according to your need here.
if view.action not in ["list"]:
return []
return super().get_schema_fields(view)
in settings.py :
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': (
'api.filter_backends.MyDjangoFilterBackend'
),
'DEFAULT_PAGINATION_CLASS': 'api.pagination.MyPagination',
'PAGE_SIZE': 20
}
I am using Django Rest Framework and have the following model:
class Picture(models.Model):
some_field = models.ForeignKey(some_model)
image = models.ImageField()
I would like to write a GET endpoint that returns the url of the image. This is what I've done so far
def get(self, request, aid):
'''
Get Image
'''
try:
picture = Picture.objects.filter(some_field=aid)
except Picture.DoesNotExist:
raise Http404
serialiser = PictureSerialiser(picture)
return Response(serialiser.data)
and my Serialiser as follows:
class PictureSerialiser(serializers.ModelSerializer):
class Meta:
model = Picture
fields = ('field', 'image')
How do I make sure that the response is {field:'Value here', image:'url to image'}?
You could do this with a custom serializer method like so:
class PictureSerialiser(serializers.ModelSerializer):
image_url = serializers.SerializerMethodField('get_image_url')
class Meta:
model = Picture
fields = ('field', 'image', 'image_url')
def get_image_url(self, obj):
return obj.image.url
Updating the image field in the serializer to use_url=True worked for me:
class PictureSerialiser(serializers.ModelSerializer):
image = serializers.ImageField(
max_length=None, use_url=True
)
class Meta:
model = Picture
fields = ('field', 'image')
I wasn't able to get the currently accepted answer (adding a custom get_image_url method to the serializer) to work in Django 2.2. I was getting error messages that I needed to update my model to include the field image_url. Even after updating the model it wasn't working.
Provided answers are all correct, But I want to add a point to answers, and that is a way to return the path of the file including the address of the site. To do that, we get help from the request itself:
class PictureSerialiser(serializers.ModelSerializer):
image_url = serializers.SerializerMethodField('get_image_url')
class Meta:
model = Picture
fields = ('field',
'image',
'image_url')
def get_image_url(self, obj):
request = self.context.get("request")
return request.build_absolute_uri(obj.image.url)
def get(self, request, aid):
'''
Get Image
'''
try:
picture = Picture.objects.filter(some_field=aid)
except Picture.DoesNotExist:
raise Http404
serialiser = PictureSerialiser(picture, context={'request': request}) # Code Added here
return Response(serialiser.data)
in your get views just add context={'request': request}. And It may work fine. My code is working and I am getting full url of Image. DRF Docs
BEST METHOD SO FAR
You must be getting the path to the image as something like this
image_field: "media/path/to/your/image.jpg"
But what you really want in response is image_field: "http://localhost/media/path/to/your/image.jpg"
STEP: 1
In your model provide attribute "upload_to" in the field:
#models.py
YourModel(models.Model):
image_field = models.ImageField(upload_to='images/', null = True)
STEP: 2
Add this in settings.py, It will ensure a media directory is created for all your images
#settings.py
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
STEP: 3
Add a configuration to your urls.py. It will ensure your images are accessible over a url.
#urls.py
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
...#some paths
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
STEP: 4
In you serializer add these lines
#serializers.py
class YourModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.YourModel
fields = '__all__'
#This is the magic function which does the work
def get_photo_url(self, obj):
request = self.context.get('request')
photo_url = obj.fingerprint.url
return request.build_absolute_uri(photo_url)
You might wonder about "context", "request", "obj" , This will make sense in step 5
STEP: 5
Now atlast in your Views.py, we have to provide the request, context to Serializer.
#views.py
class YourModelView(APIVIew):
def get(self, request, format=None):
queryset = models.YourModel.objects.all()
serializer = serializers.YourModelSerializer(queryset, context={"request":
request}, many=True)
return Response(serializer.data)
After following these steps you will be getting the required image_url in your Response
Have a nice day!!!
serializers.py
class ListImageSerializer(serializers.ModelSerializer):
class Meta:
model = UploadImg
fields = [
'id',
'image'
]
views.py
class ListImageAPIView(generics.ListAPIView):
serializer_class = ListImageSerializer
queryset = UploadImg.objects.all()
permission_classes = [IsAuthenticated]
def get(self, request, format=None):
imgobj = UploadImg.objects.all()
serializer = ListImageSerializer(imgobj, many=True, context=
{'request': request})
return Response({
'status' : True,
'message' : 'Image List',
'images' : serializer.data})
just add context= {'request': request}) line in your views.py file's serializer object and you will good to go
Response
{
"status": true,
"message": "Image List",
"images": [
{
"id": 1,
"image": "http://192.168.1.24:8000/media/images/emoji_7Om0yz7.png"
}
]
}