I've been bashing my head against the wall on this for about 3 full days now and probably read every thread on SO. Warning = I am not very good with Django REST or indeed Python.
To summarise, each user profile has 11 football players they have initially selected. I now want to update/change these players by POSTing json info.
views.py
elif request.method == 'POST':
jsondata = dict(request.data)
profile = Profile.objects.get(user=request.user)
serializer = ProfileSerializer(profile, data=jsondata, partial=True)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, status=201)
Serializers.py
class ProfileSerializer(serializers.ModelSerializer):
"""
Serializing all the Players
"""
#user = serializers.StringRelatedField()
GK1 = PlayerSerializer()
DF1 = PlayerSerializer()
DF2 = PlayerSerializer()
DF3 = PlayerSerializer()
DF4 = PlayerSerializer()
MF1 = PlayerSerializer()
MF2 = PlayerSerializer()
MF3 = PlayerSerializer()
MF4 = PlayerSerializer()
FW1 = PlayerSerializer()
FW2 = PlayerSerializer()
def create(self, validated_data):
return Profile.objects.create(**validated_data)
class Meta:
model = Profile
fields = ( "GK1", "DF1", "DF2", "DF3", "DF4", "MF1", "MF2", "MF3", "MF4", "MF5", "FW1", "FW2",)
Right now as a test I am trying to update simply GK1. I know that I am getting correct json data via POST. I also know the instance data is the original data. However it just will not save/update!!! I have overridden the update method as a test:
def update(self, instance, validated_data):
setattr(instance, "GK1.id", validated_data['GK1']['id'])
instance.save()
return instance
### set the id of GK1 to the id that arrives via POST
### GK1.id gives me '1008' - a Player object
### validated_data['GK1']['id'] gives me '1025' - id of another player
Nothing happens - the id does not change in the DB :(
Here's my main question -
validated_data is an OrderedDict (fine)
instance is a User Profile object.
How do I squeeze the OrderedDict data into the Profile object? Can I do this simply by referencing the Primary Foreign Key? I.e. id?
I've very sorry about how vague this is and perhaps the lack of code. But the DRF tutorial is not clear imho. Any help much appreciated.
EDIT - json data / validated_data added to comments
EDIT2 - I have a working solution
I've changed the update method to do the following for each player:
instance.GK1 = PlayerID.objects.get(id=validated_data['GK1'].pop('id'))
Or in other words, the instance was looking for Player OBJECTS (and not a string representation of the id). Therefore I popped out the id and filtered (or .get) by the id of the object.
Thanks for reading, I have a massive weight off my shoulders...
I've changed the update method to do the following for each player:
instance.GK1 = PlayerID.objects.get(id=validated_data['GK1'].pop('id'))
Or in other words, the instance was looking for Player OBJECTS (and not a string representation of the id). Therefore I popped out the id and filtered (or .get) by the id of the object.
Thanks for reading, I have a massive weight off my shoulders...
Related
I have informations about companies presented in a table. One of the field of this table is the mean value of each note the company received ('note_moyenne' in models.FicheIdentification).
By clicking on a button, people are able to submit a new note for the company ('note' in models.EvaluationGenerale). I want the mean value of the notes to update in the database each time someone submit a new note.
Here is my models.py :
class FicheIdentification(models.Model):
entreprise=models.ForeignKey(Entreprise, on_delete=models.CASCADE)
note_moyenne=models.IntegerField()
def __str__(self):
return self.entreprise.nom_entreprise
class EvaluationGenerale(models.Model):
entreprise=models.ForeignKey(Entreprise, on_delete=models.CASCADE)
note=models.IntegerField()
commentaires=models.CharField(max_length=1000)
date_evaluation=models.DateField(auto_now_add=True)
def __str__(self):
return self.commentaires
views.py :
class CreerEvaluationGenerale(CreateView):
form_class = FormulaireEvaluationGenerale
model = EvaluationGenerale
def form_valid(self, form):
form.instance.entreprise=Entreprise.objects.filter(siret=self.kwargs['siret']).first()
return super(CreerEvaluationGenerale, self).form_valid(form)
def get_success_url(self):
return reverse('details-evaluations')
Currently I just display the mean value in my table using this
def render_evaluation(self, record):
return (EvaluationGenerale.objects.filter(entreprise=record.entreprise.siret).aggregate(Avg('note'))['note__avg'])
but I really don't like this solution as I want the value to be stored in the database, in FicheIdentification.note_moyenne.
I thought about creating a UpdateView class but couldn't manage to link it with my CreateView.
Any help or documentation would be really appreciated, I'm a bit lost right know...
Typically, you would not store calculated fields. The usual way is not to store the average, but to use an annotation/aggregation in your query.
To centralize this to your model, you would want to write a custom model manager to implement this, so it can be reused anywhere you use your model without rewriting the logic.
class MyModelManager(models.Manager):
def note_average(self, **filter_kwargs):
qs = self.get_queryset()
# replace `...` with your aggregation as needed
return qs.filter(**filter_kwargs).aggregate(...)
class EvaluationGenerale(models.Model):
objects = MyModelManager() # override the default manager
# ... the rest of the model as-is
Then you can use something like the following in your view(s):
EvaluationGenerale.objects.note_average(entreprise=record.entreprise.siret)
See for additional reference: How to add a calculated field to a Django model
I see two ways of doing it.
Either a listener post_save on EvaluationGenerale (doc). You'll be able to compute the new average each time a new EvaluationGenerale is entered in DB.
#receiver(post_save, sender=EvaluationGenerale)
def evaluation_general_note_moyenne_computer_post_save_listener(sender, instance, **kwargs):
entreprise = instance.entreprise
entreprise.note_moyenne = entreprise.evaluationgeneral_set.aggregate(Avg('note')).values()[0])
entreprise.save()
post save listener will only trigger on instance.save() and models.objects.create() not on queryset.update() or model.objects.bulk_create().
Either overriding the save (doc) function of your form to compute the average after the creation of the new EvaluationGenerale
def save(self):
instance = super.save()
entreprise = instance.entreprise
entreprise.note_moyenne = entreprise.evaluationgeneral_set.aggregate(Avg('note')).values()[0]
entreprise.save()
return instance
Assuming there is as single FicheIdentification object per enterprise, you could update the note_moyenne field when you save the EvaluationGenerale object, like:
obj = FicheIdentification(...)
FicheIdentification.objects.filter(entreprise=record.entreprise.siret).update(note_moyenne=obj.aggregate(Avg('note'))['note__avg']
obj.save()
Please let me know if it works.
I am a novice, apologies if this question seems silly. I need to save some data into MySQL database. There are no input fields. The user should click a button, and a table is updated. The data to be saved is two foreign keys and a PK.
Here is my model
class Bids(models.Model):
id=models.AutoField(primary_key=True)
userid = models.ForeignKey(Writer, on_delete=models.DO_NOTHING, related_name='userid ')
orderid = models.ForeignKey(Orders, on_delete=models.DO_NOTHING, related_name='orderids')
biddatetime=models.DateTimeField(auto_now_add=True)
I have tried writing several functions to save these fields into table bids but no joy so far. Hers's a sample.
def saveBid(request):
if request.method!= "POST":
return HttpResponse("Action Not Allowed")
else:
biddatetime=request.POST.get('biddatetime')
bids= Bids(biddatetime=biddatetime)
order=Orders(id=id)
user= CustomUser()
user.save()
bids.save()
Pls assist
I would try sending a POST request to saveBid using Postman and what error you're getting. Post the response from postman here for more help.
It could be that
biddatetime is a string and not a datetime.
On row order=Orders(id=id) you have no variable named id in your code, this will raise error.
In your model Bids the fields userid and orderid do not allow null and blank.
You can use strptime() to convert biddatetime to datetime object.
Try something like that:
from datetime import datetime
def saveBid(request):
if request.method != "POST":
return HttpResponse("Action Not Allowed")
else:
query = request.POST
# See Format Codes - link below
biddatetime = datetime.strptime(query.get('biddatetime'), "%Y-%m-%d")
# get Order
order = Orders.objects.get(id=query.get("order_id")
# create CustomUser
user = CustomUser.objects.create(username="username")
# create Bids
bids = Bids.objects.create(biddatetime=biddatetime, userid=user, orderid=order)
create() method:
create(**kwargs)
A convenience method for creating an object and saving it all in one
step. Thus:
p = Person.objects.create(first_name="Bruce", last_name="Springsteen")
Linkt to Format Codes.
See also Creating objects.
Any reason why you are not using django Form or ModelForm?
class BidForm(forms.Form):
biddatetime = forms.DateTimeField()
.... // other fields
#require_POST
def saveBid(request):
form = BidForm(request.POST)
if form.is_valid():
biddatetime = form.cleaned_data.get('biddatetime')
... // do same for similar fields.
...// after user save
user.refresh_from_db() // post insert you will get the id value for the row
bids = Bids(
biddatetime=biddatetime,
userid=user.userid,
orderid=order.orderids)
bids.save()
I am assuming you are using the id value for user after save if thats not the case you can ignore it.
I have a very large database (6 GB) that I would like to use Django-REST-Framework with. In particular, I have a model that has a ForeignKey relationship to the django.contrib.auth.models.User table (not so big) and a Foreign Key to a BIG table (lets call it Products). The model can be seen below:
class ShoppingBag(models.Model):
user = models.ForeignKey('auth.User', related_name='+')
product = models.ForeignKey('myapp.Product', related_name='+')
quantity = models.SmallIntegerField(default=1)
Again, there are 6GB of Products.
The serializer is as follows:
class ShoppingBagSerializer(serializers.ModelSerializer):
product = serializers.RelatedField(many=False)
user = serializers.RelatedField(many=False)
class Meta:
model = ShoppingBag
fields = ('product', 'user', 'quantity')
So far this is great- I can do a GET on the list and individual shopping bags, and everything is fine. For reference the queries (using a query logger) look something like this:
SELECT * FROM myapp_product WHERE product_id=1254
SELECT * FROM auth_user WHERE user_id=12
SELECT * FROM myapp_product WHERE product_id=1404
SELECT * FROM auth_user WHERE user_id=12
...
For as many shopping bags are getting returned.
But I would like to be able to POST to create new shopping bags, but serializers.RelatedField is read-only. Let's make it read-write:
class ShoppingBagSerializer(serializers.ModelSerializer):
product = serializers.PrimaryKeyRelatedField(many=False)
user = serializers.PrimaryKeyRelatedField(many=False)
...
Now things get bad... GET requests to the list action take > 5 minutes and I noticed that my server's memory jumps up to ~6GB; why?! Well, back to the SQL queries and now I see:
SELECT * FROM myapp_products;
SELECT * FROM auth_user;
Ok, so that's not good. Clearly we're doing "prefetch related" or "select_related" or something like that in order to get access to all the products; but this table is HUGE.
Further inspection reveals where this happens on Line 68 of relations.py in DRF:
def initialize(self, parent, field_name):
super(RelatedField, self).initialize(parent, field_name)
if self.queryset is None and not self.read_only:
manager = getattr(self.parent.opts.model, self.source or field_name)
if hasattr(manager, 'related'): # Forward
self.queryset = manager.related.model._default_manager.all()
else: # Reverse
self.queryset = manager.field.rel.to._default_manager.all()
If not readonly, self.queryset = ALL!!
So, I'm pretty sure that this is where my problem is; and I need to say, don't select_related here, but I'm not 100% if this is the issue or where to deal with this. It seems like all should be memory safe with pagination, but this is simply not the case. I'd appreciate any advice.
In the end, we had to simply create our own PrimaryKeyRelatedField class to override the default behavior in Django-Rest-Framework. Basically we ensured that the queryset was None until we wanted to lookup the object, then we performed the lookup. This was extremely annoying, and I hope the Django-Rest-Framework guys take note of this!
Our final solution:
class ProductField(serializers.PrimaryKeyRelatedField):
many = False
def __init__(self, *args, **kwargs):
kwarsgs['queryset'] = Product.objects.none() # Hack to ensure ALL products are not loaded
super(ProductField, self).__init__(*args, **kwargs)
def field_to_native(self, obj, field_name):
return unicode(obj)
def from_native(self, data):
"""
Perform query lookup here.
"""
try:
return Product.objects.get(pk=data)
except Product.ObjectDoesNotExist:
msg = self.error_messages['does_not_exist'] % smart_text(data)
raise ValidationError(msg)
except (TypeError, ValueError):
msg = self.error_messages['incorrect_type'] % type(data)
raise ValidationError(msg)
And then our serializer is as follows:
class ShoppingBagSerializer(serializers.ModelSerializer):
product = ProductField()
...
This hack ensures the entire database isn't loaded into memory, but rather performs one-off selects based on the data. It's not as efficient computationally, but it also doesn't blast our server with 5 second database queries loaded into memory!
I would like to share django model history (created by django-simple-history) using tastypie.
Problem is, how to prepare ModelResource for this purpose.
Access to model history is by model.history manager. So access to all changes of model we can gain by model.history.all()
What i would like to obtain? For example. I have django model Task and the API endpoints:
http://127.0.0.1/api/v1/task - display all tasks list
http://127.0.0.1/api/v1/task/1 - display details for choosen task
http://127.0.0.1/api/v1/task/1/history - display history of task no. 1
First two links presents default behavior of ModelResource. what i have till now?
class TaskResource(ModelResource):
class Meta:
# it displays all available history entries for all task objects
queryset = Task.history.all()
resource_name = 'task'
def prepend_urls(self):
return [
url(r"^(?P<resource_name>%s)/(?P<pk>\w[\w/-]*)/history$" % (self._meta.resource_name,),
self.wrap_view('get_history'),
name="api_history"),
]
def get_history(self, request, **kwargs):
#...
get_history should return bundle with history entries.. but how this method should look?
I guess, i need to create bundle with needed data, but don't know how exactly should i do that.
Does someeone have experience with simple-history and tastypie to present some simple example?
It seems, solution was simpler than i thought. Maybe someone use this in feature:
class TaskHistoryResource(ModelResource):
class Meta:
queryset = Task.history.all()
filtering = { 'id' = ALL }
class TaskResource(ModelResource):
history = fields.ToManyField(AssetTypeHistoryResource, 'history')
class Meta:
# it displays all available history entries for all task objects
queryset = Task.history.all()
resource_name = 'task'
def prepend_urls(self):
return [
url(r"^(?P<resource_name>%s)/(?P<pk>\w[\w/-]*)/history$" %(self._meta.resource_name,),
self.wrap_view('get_history'),
name="api_history"),
]
def get_history(self, request, **kwargs):
try:
bundle = self.build_bundle(data={'pk': kwargs['pk']}, request=request)
obj = self.cached_obj_get(bundle=bundle, **self.remove_api_resource_names(kwargs))
except ObjectDoesNotExist:
return HttpGone()
except MultipleObjectsReturned:
return HttpMultipleChoices("More than one resource is found at this URI.")
history_resource = TaskHistoryResource()
return history_resource.get_list(request, id=obj.pk)
A bit changed solution from:
http://django-tastypie.readthedocs.org/en/latest/cookbook.html#nested-resources
Basically, there was need to create additional resource with history entries. get_history method creates and returns instance of it with appropriate filter on id field (in django-simple-history id field contain id of major object. Revision primary key names history_id)
Hope, that will help someone.
I have a form with models and foreign key modes represented as inline formsets.
I'm having a helluva time saving the ordered formsets. In fact, every time I try to delete one, it gets multiplied.
in forms.py:
class PublicationForm(ModelForm):
class Meta:
model = Publication
fields = ['title']
SectionFormSet = inlineformset_factory(Publication, Section, can_delete=True, can_order=True, extra=2)
and in views.py:
if publication_form.is_valid():
pub = publication_form.save(commit=False)
section_formset = SectionFormSet(request.POST, instance=pub, prefix='section')
if section_formset.is_valid():
pub.save()
for s in section_formset.ordered_forms:
s.instance.order = s.cleaned_data['ORDER']
s.save()
I've loked on S.O. but found nothing.
Does anybody have a solution?
Thanks!!
Yes, you have the saving all jumbled up. Something like this:
1 - Your formset holds the sets of SectionForm, so you are testing for the form to be valid in the wrong loop. You want this:
if request.method == 'POST':
section_formset = SectionFormSet(request.POST, instance=<an instance of Publication>, prefix='section')
if section_formset.is_valid():
instances = section_formset.save(commit=False)
for instance in instances:
#do something
instance.save()
return HttpResponseRedirect('<a url>')
else:
section_formset = SectionFormSet(instance=<an instance of Publication>, prefix='section')
You don't need to do the instances loop if you're just saving the section forms 'order' value to the section models order attr - it's done for you. You can just call section_formset.save().