How to create seperate POST request for imagefield in DRF - django

I need to make a separate API for the image file. How can I achieve this?
models.py
class Organization(models.Model):
code = models.CharField(max_length=25, null=False, unique=True)
name = models.CharField(max_length=100, null=False)
location = models.ForeignKey(Location, on_delete=models.RESTRICT)
description = models.TextField()
total_of_visas = models.IntegerField(null=False, default=0)
base_currency = models.ForeignKey(Currency, on_delete=models.RESTRICT)
logo_filename = models.ImageField(upload_to='uploads/')
serializers.py
class OrganizationSerializer(serializers.ModelSerializer):
location = serializers.CharField(read_only=True, source="location.name")
base_currency = serializers.CharField(read_only=True, source="base_currency.currency_master")
location_id = serializers.IntegerField(write_only=True, source="location.id")
base_currency_id = serializers.IntegerField(write_only=True, source="base_currency.id")
class Meta:
model = Organization
fields = ["id", "name", "location", "mol_number", "corporate_id", "corporate_name",
"routing_code", "iban", "description", "total_of_visas", "base_currency", "logo_filename",
"location_id", "base_currency_id"]
def validate(self, data):
content = data.get("content", None)
request = self.context['request']
if not request.FILES and not content:
raise serializers.ValidationError("Content or an Image must be provided")
return data
def create(self, validated_data):
....
def update(self, instance, validated_data):
....
views.py
class OrganizationViewSet(viewsets.ModelViewSet):
queryset = Organization.objects.all()
serializer_class = OrganizationSerializer
lookup_field = 'id'
urls.py
router = routers.DefaultRouter()
router.register('organization', OrganizationViewSet, basename='organization')
urlpatterns = [
path('', include(router.urls)),
]+static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
I don't have idea how to make POST request for image field from postman. I've been stuck here for a long time. Any help is appreciated.

if you want to update the image field separately you just have to create a separate serializer for it
class OrganizationImageSerializer(serializers.ModelSerializer):
logo_filename = serializers.ImageField()
class Meta:
model = Organization
fields = ["logo_filename"]
view.py
class OrganizationImageView(generics.UpdateAPIView):# or `generics.RetrieveUpdateAPIView` if you also want to retriev the current one before updating it
serializer_class = OrganizationImageSerializer
permission_classes = [IsAuthenticated, ]
def get_queryset(self):
queryset = Organization.objects.filter(id=self.kwargs['pk'])
return queryset
urls.py
from .views import OrganizationImageView
urlpatterns = [
...
path('update_org/<int:pk>', OrganizationImageView.as_view(), name='OrganizationImageUpdater'),
]

Related

Django Rest Framework how to display related models as one json file

I have a few different models with a fact table of scenarios and 6 dimension tables that relate to it below as examples:
class fScenario(models.Model):
#Variables
scenarioId = models.IntegerField(primary_key=True)
description = models.CharField(max_length=100)
def __str__(self):
return str(self.scenarioId)
def get_absolute_url(self):
return reverse('scenario-detail', args=[str(self.scenarioId)])
class Meta:
ordering = ['scenarioId']
class dADA(models.Model):
#Variables
scenarioId = models.ForeignKey(fScenario, on_delete=models.CASCADE)
dateTimeId = models.DateTimeField('ADA Time/Date')
latitutde = models.FloatField(default=0)
longitude = models.FloatField(default=0)
instanceType = models.CharField(max_length=50, default='ADA')
def __str__(self):
return f'{self.scenarioId}, {self.instanceType}'
class Meta:
ordering = ['dateTimeId']
serializers.py
class fScenarioSerializer(serializers.ModelSerializer):
class Meta:
model = fScenario
fields = ['scenarioId', 'description']
class dADASerializer(serializers.ModelSerializer):
scenarioId = fScenarioSerializer(read_only=True)
class Meta:
model = dADA
fields = ['scenarioId', 'dateTimeId', 'latitutde', 'longitude', 'instanceType']
urls.py
router = routers.DefaultRouter()
router.register(r'fScenario', views.fScenarioViewSet)
router.register(r'dADA', views.dADAViewSet)
urlpatterns = [
path('', include(router.urls)),
views.py
class fScenarioViewSet(viewsets.ModelViewSet):
queryset = fScenario.objects.all()
serializer_class = fScenarioSerializer
class dADAViewSet(viewsets.ModelViewSet):
queryset = dADA.objects.all()
serializer_class = dADASerializer
I'm using rest framework view sets and I currently can see in my API separate fScenario and dADA views but cant figure out how to link the dADA to the fScenario view on one page.
You can use SerializerMethodField and Django many-to-one backward relation to retrieve related objects:
serializers.py
class fScenarioSerializer(serializers.ModelSerializer):
dADA_list = serializers.SerializerMethodField()
class Meta:
model = fScenario
fields = ['scenarioId', 'description', 'dADA_list']
def get_dADA_list(self, obj):
return obj.dada_set.all().values()

POST data only if you are authenticated and only to your user using django-rest-framework

i'm new to Django so sorry if this seems stupid.
i want to add an item to a database only if the user is authenticated .
here are the models:
class SaleItems(models.Model):
product_name = models.CharField(max_length=50)
price = models.IntegerField()
product_type = models.CharField(max_length=25)
description = models.CharField(max_length=250 ,default='', blank=True)
brand = models.CharField(max_length=25, null=True,blank=True)
image_path = models.ImageField(upload_to='images/product_image')
date_added = models.DateField(auto_now_add=True)
in_stock = models.BooleanField(default=True)
def __str__(self):
return f"{self.product_name}, price={self.price}"
class SaleHistory(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
product = models.ForeignKey(SaleItems, on_delete=models.RESTRICT, default=None)
date_bought = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f'{self.date_bought}, {self.product}, {self.user}'
the serializers:
class SaleItemSerializer(serializers.ModelSerializer):
class Meta:
model = SaleItems
fields = '__all__'
class SaleHistorySerializier(serializers.ModelSerializer):
class Meta:
model = SaleHistory
fields = '__all__'
urls:
routes = routers.DefaultRouter()
routes.register('api/saleitems', SaleItemViewSet, basename='saleitem')
routes.register('api/salehistory', SaleHistoryViewSet, basename='salehistory')
urlpatterns = [ path('',include(routes.urls))
]
and finally the apis
class SaleItemViewSet(viewsets.ModelViewSet):
queryset = SaleItems.objects.all()
permission_classes = [permissions.AllowAny]
serializer_class = SaleItemSerializer
class SaleHistoryViewSet(viewsets.ModelViewSet):
# queryset = SaleHistory.objects.all()
permission_classes = [permissions.IsAuthenticated]
serializer_class = SaleHistorySerializier
def get_queryset(self):
user = self.request.user
return SaleHistory.objects.filter(user = user)
so the problem, when i post to 'api/salehistory' i am able to add content to any user not only as the authenticated user.
(using knox authtoken for authentication).
Say for example i am authenticated as user1 and i have my auth token. Now I can use that token to add items to the SaleHistory model for any user which is very much undesired.
how can i solve this?
once again sorry for the crude description. first time asking question here.
First, set the user field as read_only by using read_only_fields meta option
class SaleHistorySerializier(serializers.ModelSerializer):
class Meta:
model = SaleHistory
fields = '__all__'
read_only_fields = ("user",)
Now, the SaleHistorySerializier will not accept the data from the user field.
Then, You need to override the perform_create(...) method of the SaleHistoryViewSet class
class SaleHistoryViewSet(viewsets.ModelViewSet):
# queryset = SaleHistory.objects.all()
permission_classes = [permissions.IsAuthenticated]
serializer_class = SaleHistorySerializier
def get_queryset(self):
return SaleHistory.objects.filter(user=self.request.user)
def perform_create(self, serializer):
serializer.save(user=self.request.user)

Django REST Framework - Query models by field value

I have a Building model for which I have created a Serializer and ModelViewSet. Now I am able to get all Buildings with /api/buildings/ and get a Building object with /api/buildings/3 where 3 is the Building Id.
How do I query for a Building object with a particular attribute value? For example, how do I get the Building object with name == "hospital"? Something like /api/buildings?name=hospital does not work.
views.py
class APIBuildingsViewSet(viewsets.ModelViewSet):
queryset = Building.objects.all()
serializer_class = BuildingSerializer
models.py
class Building(models.Model):
building_id = models.AutoField('ID', auto_created=True, primary_key=True)
name = models.CharField('Name', max_length=125, null=True, blank=False, unique=False)
address = models.CharField('Address', max_length=256, null=False, blank=False)
user_id = models.ForeignKey('accounts.User', on_delete=models.CASCADE, default=1)
def __str__(self):
return self.name
serializers.py
class BuildingSerializer(serializers.ModelSerializer):
class Meta:
model = Building
fields = ('building_id', 'name', 'address', 'user_id')
urls.py
router = DefaultRouter()
router.register(r'buildings', views.APIBuildingsViewSet, base_name='buildings')
urlpatterns = [
url(r'^api/', include(router.urls)),
]
You need to use DjangoFilterBackend in your view, and specify which fields can be used to filter.
First you must install it with pip install django-filter, and add django_filters to settings.py/INSTALLED_APPS, and set your view like this:
...
from django_filters.rest_framework import DjangoFilterBackend
class APIBuildingsViewSet(viewsets.ModelViewSet):
queryset = Building.objects.all()
serializer_class = BuildingSerializer
filter_backends = (DjangoFilterBackend,)
filter_fields = (building_id, name, address, user_id)
Whit these settings, you would be able to send a post like /api/buildings/?name=hospital (be aware of the slash before the question mark)
just change in your view.py file
views.py
from django_filters.rest_framework import DjangoFilterBackend
class APIBuildingsViewSet(viewsets.ModelViewSet):
queryset = Building.objects.all()
serializer_class = BuildingSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['name', 'address']

Why do I get 'detail: not found' for primary key formatted as '12-345.78'?

When I try to display detail info calling e.g. /api/products/12-345.67/, I get detail: Not found. as a response. As you can see, product IDs are formatted as 12-345.67. My first suspect was the RegEx validator (listed below), but it works the same way with or without it.
Model, serializers, viewsets and URLs are defined this way:
# models.py:
class Product(models.Model):
product_id = models.CharField(max_length=9, primary_key=True)
name = models.CharField(max_length=40)
is_active = models.BooleanField(default=True)
def __str__(self):
return self.product_id
# serializers.py:
class ProductSerializer(serializers.ModelSerializer):
product_id = serializers.RegexField(regex='^\d{2}-\d{3}\.\d{2}$', max_length=9, min_length=9, allow_blank=False)
name = serializers.CharField(min_length=6, max_length=50, allow_blank=False)
class Meta:
model = Product
fields = '__all__'
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
lookup_field = 'product_id'
# urls.py:
router = routers.DefaultRouter()
router.register(r'products', ProductViewSet, basename='products')
(...)
urlpatterns = [
path('api/', include(router.urls)),
(...)
I don't think you should be having full stops (.) in you API endpoints. Probably change the id to something else. & Zapraszamy :D

Nested router not working

I'm new to Django and am struggling to get nested routers working. Basically, I'm trying to model the endpoint /api/v1/organizers/1/events/1, where I have Event and Organizer models as follows:
class Event(models.Model):
class Meta:
db_table = 'events'
STATUS_CHOICES = (
('scheduled', 'Scheduled'),
('completed', 'Completed'),
('cancelled', 'Cancelled')
)
name = models.TextField()
purpose = models.TextField()
date_time = models.DateTimeField()
description = models.TextField()
programme = models.TextField()
entry_fee = models.DecimalField(max_digits=6, decimal_places=2)
status = models.TextField(choices=STATUS_CHOICES)
comments = models.TextField(null=True)
people_invited = models.ManyToManyField('Contact', through='EventInvitation')
organizer = models.ForeignKey('Organizer', on_delete=models.CASCADE)
class Organizer(models.Model):
STATUS_CHOICES = (
('inactive', 'Inactive'),
('active', 'Active'),
)
class Meta:
db_table = 'organizers'
first_name = models.TextField()
middle_name = models.TextField(null=True)
last_name = models.TextField(null=True)
email = models.OneToOneField('Email', on_delete=models.CASCADE)
company_name = models.TextField(null=True)
company_description = models.TextField(null=True)
password = models.TextField()
login_token = models.TextField(null=True)
registered_on = models.DateTimeField(null=True)
status = models.TextField(choices = STATUS_CHOICES, default='inactive')
I created another app called rest_api to handle the API. The models are stored in an app called shared_stuff. Anyway, here's the project-level urls.py (don't mind the front_end app for now):
from django.conf.urls import include, url
urlpatterns = [
url(r'^api/v1/', include('rest_api.urls')),
url(r'^', include('frontend.urls')),
]
And here's the urls.py from the app rest_api:
from django.conf.urls import url, include
from rest_framework_nested import routers
from .views import *
router = routers.SimpleRouter()
# /organizers/12/events/1
router.register(r'organizers', OrganizerViewSet, base_name='organizers')
organizer_router = routers.NestedSimpleRouter(router, r'organizers', lookup='organizers')
organizer_router.register(r'events', EventViewSet, base_name='organizers-events')
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^', include(organizer_router.urls)),
]
Here's the serializers.py for rest_api app:
class EventSerializer(serializers.ModelSerializer):
class Meta:
model = Event
fields = ['id', 'name', 'purpose', 'date_time', 'description', 'programme', 'entry_fee', 'status', 'comments']
class OrganizerSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Organizer
fields = ['id', 'first_name', 'middle_name', 'last_name', 'email', 'company_name', 'company_description', 'registered_on', 'status']
events = serializers.HyperlinkedIdentityField(
view_name = 'events_list',
lookup_url_kwarg = 'organizer_pk'
)
And finally, here's the views.py from the app rest_api:
from rest_framework import viewsets
from .models import *
from .serializers import *
class EventViewSet(viewsets.ModelViewSet):
def list(self, request, organizer_pk=None, name='events_list'):
events = self.queryset.filter(organizer=organizer_pk)
serializer = EventSerializer(events, many=True)
return Response(serializer.data)
class OrganizerViewSet(viewsets.ModelViewSet):
def list(self, request, name='organizers_list'):
data = Organizer.objects.all()
serializer = OrganizerSerializer(data, many=True)
return Response(serializer.data)
I'm sure there are many things broken in my code, and that's where I need help. The problem is I'm getting the following error:
TypeError: list() got an unexpected keyword argument 'organizers_pk'
I'm not sure what's wrong, and will appreciate some help!
I got it working by changing the EventViewSet to the following:
def list(self, request, organizers_pk=None, name='events_list'):
events = self.queryset.filter(organizer=organizers_pk)
serializer = EventSerializer(events, many=True)
return Response(serializer.data)
I'm not sure why, but the expected keyword argument name is organizers_pk, whereas I had organizer_pk. I would like to know why this is so, but other than that my problem is solved for now.