Django Rest Framework - Cannot POST because username already exists - django

I am working on a basic rest api with django rest framework. And my database is on MySQL. For one of the functions, when I try to POST, it gives me an error because there is a row with the same username already. I have set the model to have all three of its fields to be unique_together. Please help me understand where i am going wrong, heres snippets of what i have currently:
models.py:
class VolunteerHours(models.Model):
username = models.OneToOneField(Volunteer, models.DO_NOTHING, db_column='Username', primary_key=True)
date = models.DateField(db_column='Date')
hours = models.IntegerField(db_column='Hours')
class Meta:
managed = False
db_table = 'volunteer_hours'
unique_together = (('username', 'date', 'hours'),)
urls.py:
urlpatterns = [
path('timesheet/', views.TimesheetAPIView.as_view()),]
views.py:
from . import models
from . import serializers
from rest_framework import generics
from rest_framework import mixins
class TimesheetAPIView(generics.GenericAPIView, mixins.CreateModelMixin):
serializer_class = serializers.VolunteerTimesheetSerializer
def post(self, request):
return self.create(request)
serializers.py:
class VolunteerTimesheetSerializer(serializers.ModelSerializer):
class Meta:
model = models.VolunteerHours
fields = '__all__'
The error im getting is:
"username": [
"volunteer hours with this username already exists."
]
What I want to happen is i can add as many rows with the same username as long as the date and hours are unique. Which are what is submit in my POST requests, but it says username already exists even if the date and hours are unique.
Please help.
Thank you!

You’ve created a OneToOne relationship with VolunteerHours and Volunteers, meaning only one of each can exist in a relationship. Like #DEEPAK KUMAR says, you’ll want to remove primary_key=True, but you’ll also want to change the relationship to be OneToMany via the ForeignKey field.

Related

New attributes are not showing up in django admin dashboard

Previously I was using my project with sqlite. Then started a new project copied the data from previous project and made some changes, and I'm using this with mysql.
This is my models.py(not full)
from django.db import models
from django.db.models import CheckConstraint, Q, F
class College(models.Model):
CITY_CHOICES=[('BAN','Bangalore')]
id=models.IntegerField(primary_key=True)
name=models.CharField(max_length=50)
city=models.CharField(choices=CITY_CHOICES,default='BAN',max_length=10)
fest_nos=models.IntegerField()
image=models.ImageField(default='default.jpg',upload_to='college_pics')
class Meta():
db_table='college'
def __str__(self):
return self.name
class Organizer(models.Model):
id=models.IntegerField(primary_key=True)
name=models.CharField(max_length=25)
phone=models.IntegerField()
def __str__(self):
return self.name
class Fest(models.Model):
FEST_CHOICES=[
('CUL','Cultural'),
('TEC','Technical'),
('COL','College'),
('SPO','Sports'),
]
id=models.IntegerField(primary_key=True)
name=models.CharField(max_length=50)
clg_id=models.ForeignKey(College,on_delete=models.CASCADE)
fest_type=models.CharField(choices=FEST_CHOICES,default='COL',max_length=10)
fest_desc=models.TextField(default='This is a fest')
#below two field are not showing up in admin page
start_date=models.DateField(auto_now_add=True)
end_date=models.DateField(auto_now_add=True)
event_nos=models.IntegerField()
org_id=models.ManyToManyField(Organizer)
image=models.ImageField(default='default.jpg',upload_to='fest_pics')
class Meta:
constraints = [
CheckConstraint(
check = Q(end_date__gte=F('start_date')),
name = 'check_start_date',
)
]
db_table='fest'
def __str__(self):
return self.name
The start_date and end_date attributes are the new ones added in this project. It was not there in the old one.
My admin.py file
from django.contrib import admin
from .models import College, Event, Fest, Organizer, Participated
admin.site.register(College)
admin.site.register(Organizer)
admin.site.register(Fest)
admin.site.register(Event)
admin.site.register(Participated)
But in my admin dashboard, while adding new fests I'm not getting the option to add start and end date.
I made migrations once again, fake migrated etc. What to do?
Is check constraint under model fest causing this problem?
They fields won't show up on Django Admin because they have auto_now_add=True so, the user shouldn't touch them.
You can make auto_now_add field display in admin by using readonly_fields in the admin class(this only show the data, you still can't edit it because it's auto_now_add)
#register with a class to use
class FestAdmin(admin.ModelAdmin):
readonly_fields = ('start_date', 'end_date')
admin.site.register(Fest, FestAdmin)

Reload choices dynamically when using MultipleChoiceFilter

I am trying to construct a MultipleChoiceFilter where the choices are the set of possible dates that exist on a related model (DatedResource).
Here is what I am working with so far...
resource_date = filters.MultipleChoiceFilter(
field_name='dated_resource__date',
choices=[
(d, d.strftime('%Y-%m-%d')) for d in
sorted(resource_models.DatedResource.objects.all().values_list('date', flat=True).distinct())
],
label="Resource Date"
)
When this is displayed in a html view...
This works fine at first, however if I create new DatedResource objects with new distinct date values I need to re-launch my webserver in order for them to get picked up as a valid choice in this filter. I believe this is because the choices list is evaluated once when the webserver starts up, not every time my page loads.
Is there any way to get around this? Maybe through some creative use of a ModelMultipleChoiceFilter?
Thanks!
Edit:
I tried some simple ModelMultipleChoice usage, but hitting some issues.
resource_date = filters.ModelMultipleChoiceFilter(
field_name='dated_resource__date',
queryset=resource_models.DatedResource.objects.all().values_list('date', flat=True).order_by('date').distinct(),
label="Resource Date"
)
The HTML form is showing up just fine, however the choices are not accepted values to the filter. I get "2019-04-03" is not a valid value. validation errors, I am assuming because this filter is expecting datetime.date objects. I thought about using the coerce parameter, however those are not accepted in ModelMultipleChoice filters.
Per dirkgroten's comment, I tried to use what was suggested in the linked question. This ends up being something like
resource_date = filters.ModelMultipleChoiceFilter(
field_name='dated_resource__date',
to_field_name='date',
queryset=resource_models.DatedResource.objects.all(),
label="Resource Date"
)
This also isnt what I want, as the HTML now form is now a) displaying the str representation of each DatedResource, instead of the DatedResource.date field and b) they are not unique (ex if I have two DatedResource objects with the same date, both of their str representations appear in the list. This also isnt sustainable because I have 200k+ DatedResources, and the page hangs when attempting to load them all (as compared to the values_list filter, which is able to pull all distinct dates out in seconds.
One of the easy solutions will be overriding the __init__() method of the filterset class.
from django_filters import filters, filterset
class FooFilter(filterset.FilterSet):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
try:
self.filters['user'].extra['choices'] = [(d, d.strftime('%Y-%m-%d')) for d in sorted(
resource_models.DatedResource.objects.all().values_list('date', flat=True).distinct())]
except (KeyError, AttributeError):
pass
resource_date = filters.MultipleChoiceFilter(field_name='dated_resource__date', choices=[], label="Resource Date")
NOTE: provide choices=[] in your field definition of filterset class
Results
I tested and verified this solution with following dependencies
1. Python 3.6
2. Django 2.1
3. DRF 3.8.2
4. django-filter 2.0.0
I used following code to reproduce the behaviour
# models.py
from django.db import models
class Musician(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return f'{self.name}'
class Album(models.Model):
artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
release_date = models.DateField()
def __str__(self):
return f'{self.name} : {self.artist}'
# serializers.py
from rest_framework import serializers
class AlbumSerializer(serializers.ModelSerializer):
artist = serializers.StringRelatedField()
class Meta:
fields = '__all__'
model = Album
# filters.py
from django_filters import rest_framework as filters
class AlbumFilter(filters.FilterSet):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.filters['release_date'].extra['choices'] = self.get_album_filter_choices()
def get_album_filter_choices(self):
release_date_list = Album.objects.values_list('release_date', flat=True).distinct()
return [(date, date) for date in release_date_list]
release_date = filters.MultipleChoiceFilter(choices=[])
class Meta:
model = Album
fields = ('release_date',)
# views.py
from rest_framework.viewsets import ModelViewSet
from django_filters import rest_framework as filters
class AlbumViewset(ModelViewSet):
serializer_class = AlbumSerializer
queryset = Album.objects.all()
filter_backends = (filters.DjangoFilterBackend,)
filter_class = AlbumFilter
Here I've used the django-filter with DRF.
Now, I populated some data through Django Admin console. After that, the album api become as below,
and I got the release_date as
Then, I added new entry through Django admin -- (Screenshot) and I refresh the DRF API endpoint and the possible choices became as below,
I have looked into your problem and I have following suggestions
The Problem
You have got the problem right. Choices for your MultipleChoiceFilter are calculated statically whenever you run server.Thats why they don't get updated dynamically whenever you insert new instance in DatedResource.
To get it working correctly, you have to provide choices dynamically to MultipleChoiceFilter. I searched in documentation but did not find anything regarding this. So here is my solution.
The solution
You have to extend MultipleChoiceFilter and create your own filter class. I have created this and here it is.
from typing import Callable
from django_filters.conf import settings
import django_filters
class LazyMultipleChoiceFilter(django_filters.MultipleChoiceFilter):
def get_field_choices(self):
choices = self.extra.get('choices', [])
if isinstance(choices, Callable):
choices = choices()
return choices
#property
def field(self):
if not hasattr(self, '_field'):
field_kwargs = self.extra.copy()
if settings.DISABLE_HELP_TEXT:
field_kwargs.pop('help_text', None)
field_kwargs.update(choices=self.get_field_choices())
self._field = self.field_class(label=self.label, **field_kwargs)
return self._field
Now you can use this class as replacement and pass choices as lambda function like this.
resource_date = LazyMultipleChoiceFilter(
field_name='dated_resource__date',
choices=lambda: [
(d, d.strftime('%Y-%m-%d')) for d in
sorted(resource_models.DatedResource.objects.all().values_list('date', flat=True).distinct())
],
label="Resource Date"
)
Whenever instance of filter will be created choices will be updated dynamically. You can also pass choices statically (without lambda function) to this field if want default behavior.

django rest framework access item by lookup field instead of pk 3.4 DRF

I need to have lookup field in order my frontend sends email which should be deleted but I get item not found. I've researched a lot about this problem but I can't figure out which DRF version what supports.
class EmailReminderSerializer(serializers.ModelSerializer):
city = serializers.CharField(max_length=255)
url = serializers.HyperlinkedIdentityField(
view_name='web:email_reminder-detail',
)
class Meta:
model = EmailReminder
fields = '__all__'
extra_kwargs = {
'url': {'lookup_field': 'email'}
}
Now I have url but it points to instance pk, not by my desired lookup field.
Any suggestions of how it works in 3.4 version or do you have any other solutions to some lower version >=3.0?
Oh okay, I got it. For serialized models you only need lookup_field in your view but for hyperlinked serialized models you need extra_kwargs in serializers plus lookup field in views. Hope it helps someone
You should modify the lookup field in your view instead. As shown in DRF docs, you can do the following.
in views.py
from rest_framework import viewsets
class EmailReminderViewSet(viewsets.ModelViewSet):
serializer_class = TagSerializer
lookup_field = 'email'

DetailView using two ForeignKey kwargs

I am trying to utilize Django's class-based generic DetailView by querying the table using two keyword arguments passed via the url. I have tried overriding both the get_queryset() and get_object() method to no avail. My models look like this (edited for brevity, but let me know if something important is missing):
# models
class Skill(models.Model):
skill = models.CharField()
class User(AbstractBaseUser):
username = models.CharField()
class UserSkills(models.Model):
skill = models.ForeignKey(Skill)
user = models.ForeignKey(User, to_field='username')
value = models.CharField()
my url for the DetailView looks like this:
url(
regex=r"^(?P<username>[a-zA-Z0-9-]{1,25})/skills/(?P<skill>[a-zA-Z0-9 -._/]+)/$",
view=views.UserSkillDetail.as_view(),
name='userskill_detail',
),
the view:
class UserSkillDetail(DetailView):
template_name = 'UserSkill_detail.html'
context_object_name = 'skill'
model = UserSkills
def get_object(self, queryset=None):
get_user = self.kwargs['username']
get_skill = self.kwargs['skill']
return get_object_or_404(UserSkills, user__username=get_user, skill__skill=get_skill)
I keep receiving the following error via the template debug message:
No UserSkills matches the given query.
although I am able to successfully query the following via the shell:
>>> x = UserSkills.objects.get(user__username='user1', skill__skill='skill1')
>>> x
<UserSkills: user1#example.com: skill1>
and have verified that the keyword arguments are being captured correctly ('user1', 'skill1') via the logger and Django debug toolbar. Any help would be greatly appreciated!
There was an issue with my migration sequence (called out of intended order). I reconfigured the migrations and everything worked.

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()