I would like to pass a number to my generic view (DetailView) to get one object Here is my code
Urlpattern
(r'^newreportview/(?P<number>\w+)/$', NewReportView.as_view()),
View Class
class NewReportView(DetailView):
template_name = "report/newreportview.html"
context_object_name = "newreportview"
def get_queryset(self):
task= get_object_or_404(MyTask,applicationnnumber=self.args[0])
return task
I guess something is wrong in this line
name = get_object_or_404(MyTask,applicationnnumber=self.args[0])
error message:
Exception Type: IndexError
Exception Value:
tuple index out of range
How should I pass 'number' to this generic view and get a Mytask object with this 'number'?
Thanks
You have missed the entire point of generic views. For a simple DetailView - which is a view of a single object - you just define the model and slug attributes in the class:
(r'^newreportview/(\d+)/$', NewReportView.as_view()),
class NewReportView(DetailView):
template_name = "report/newreportview.html"
model = MyTask
slug = 'applicationnnumber'
(You could also just as easily have passed those three as parameters in the URL definition, so no need to make a subclass at all.)
The reason why you were getting no values for self.args is that you had passed your parameter as a kwarg, not an arg. So self.kwargs['number'] would have worked, as would the revised URL I've shown here.
Related
I have a JSONField that I need to apply a default dictionary to. As per the documentation, I am avoiding passing the mutable dictionary to the default field. This is done by instead passing the copy method to the default argument like such:
default_dict = {'some_key': 'some value'}
class MyModel(models.Model):
my_field = models.JSONField(default=default_dict.copy)
When applying makemigrations, this is failing because of the following condition in django.db.migrations.serializer.FunctionTypeSerializer:
if self.value.__module__ is None:
raise ValueError("Cannot serialize function %r: No module" % self.value)
I can get around this by defining a callable that returns a copy, but I think this is adding unnecessary syntax and makes it harder to read:
class ADict(dict):
def __call__(self):
return self.copy()
default_dict = ADict({'some_key': 'some value'})
class MyModel(models.Model):
my_field = models.JSONField(default=default_dict)
Is there a way to pass a built-in objects method as the default value for a Django field?
You can't do this since it basically needs to be a named function, whereas default_dict.copy is an "anonymous" function.
You can however make a named function like:
default_dict = {'some_key': 'some value'}
def copy_default_dict():
return default_dict.copy()
class MyModel(models.Model):
my_field = models.JSONField(default=copy_default_dict)
or even simpler:
def copy_default_dict():
return {'some_key': 'some value'}
class MyModel(models.Model):
my_field = models.JSONField(default=copy_default_dict)
My Content model has a many-to-many relationship to the Tag model. When I save a Content object, I want to add the relationships dynamically. Im doing this the following way.
def tag_content(obj):
obj.tags.add([1,2,3])
obj.is_tagged = True
obj.save()
return obj
class Tag(models.Model):
name = models.CharField(max_length=255)
class Content(models.Model):
title = models.CharField(max_length=255)
is_tagged = models.BooleanField(default=False)
tags = models.ManyToManyField(Tag, blank=True)
def save(self, *args, **kwargs):
super(Content, self).save(*args, **kwargs)
#receiver(post_save, sender = Content)
def update_m2m_relationships_on_save(sender, **kwargs):
if not kwargs['instance'].is_tagged:
tag_content(kwargs['instance'])
Basically, when a Content object is saved, the receiver is used to call a post_save method which in turn calls the tag_content method to add the m2m relationships. However, I get this error:
TypeError
unhashable type: 'list'
It specifically references the .add() function. Any idea why I am getting this error? Any help is appreciated. Also, do note that I have Tag objects with ids = 1, 2, and 3 in database.
EDIT
Alright, I changed my tag_content to something like this:
def tag_content(obj):
for tag in Tag.objects.all():
print tag
obj.tags.add(tag)
This is because the add() method takes in object instances, not ids. However, it still doesn't work :/ I get no error, but the relationships are simply not established.
This is especially weird since the print tag command works and prints out the tags. In other words, the function is being called. Any help please? Btw, I am running Django 1.9.8.
You cannot pass a list to add(), which is why you get the error. You either need to add one item at a time or expand the list into a series of arguments, e.g:
obj.tags.add(*[1,2,3]) # The * expands the list into the function arguments
This will still cause an error because you cannot pass IDs to add() - you have to pass Tag objects to it. So something like this would work:
# Get a list of Tag objects
tags_to_add = [Tag.objects.get(id=j) for j in [1, 2, 3]]
# Now pass this to the add() function:
obj.tags.add(*tags_to_add)
According to the add docs you need actual models to be passed as args.
If you really want a list of models you need to unpack it, but you might as well pass the objs as arguments directly in this case.
I have a serializer that calls other serializer, and this other serializer is a custom one that overrides the .to_representation() behaviour. How am I supposed to call this custom serializer from the first one, if I can't have access to the data sent from the view to the first serializer?
This is a quick example of the situation:
class OtherSerializer(serializers.Serializer):
def to_representation(self, obj):
# ... can't get data
class NestedSerializer(serializers.Serializer):
someotherfield = OtherSerializer(somedata, many=True) # this fails because obviously can't read "somedata", how to get access to it?
boolfield = BooleanField()
NestedSerializer(data={'someotherfield': somedata, 'boolfield': False}) # this somedata is the one I'm talking above
Can you try to remove the parameter somedata? Your code should look like:
class NestedSerializer(serializers.Serializer):
someotherfield = OtherSerializer(many=True)
boolfield = BooleanField()
I would presume that the key 'someotherfield' is referring to a list, otherwise you wouldn't need many=True.
Let me know how is that working out for you.
Suppose I have a resource like below..
class PostResource(ModelResource):
children = fields.ToManyField('MyApp.api.resources.PostResource',
attribute='comments', full=True, null=True)
Basically, I want to return this children field only and flatten it.
It will look like
[ {child-1-data}, {child-2-data} ]
rather than
{ children: [ {child-1-data}, {child2-data} ] }
How can I do that?
Additionaly, if I want a different representation of the same model class, should I create a new resource class as bellow?
class PostNormalResource(ModelResource):
class Meta:
queryset= models.Post.objects.all()
fields = ['text', 'author']
Not really the answer you are looking for but some discoveries I made while digging.
Normally you would modify the bundle data in dehydrate. See the tastypie cookbook.
def dehydrate(self, bundle):
bundle.data['custom field'] = "This is some additional text on the resource"
return bundle
This would suggest you could manipulate your PostResource's bundle data along the lines of:
def dehydrate(self, bundle):
# Replace all data with a list of children
bundle.data = bundle.data['children']
return bundle
However this will error, AttributeError: 'list' object has no attribute 'items', as the tastypie serializer is looking to serialize a dictionary not a list.
# "site-packages/tastypie/serializers.py", line 239
return dict((key, self.to_simple(val, options)) for (key, val) in data.data.items())
# .items() being for dicts
So this suggests you need to look at different serializers. (Or just refer to post['children'] when processing your JSON :-)
Hope that helps get you in the right direction
And secondly yes, if you want a different representation of the same model then use a second ModelResource. Obviously you can subclass to try and avoid duplication.
You could try overriding the alter_detail_data_to_serialize method. It it called right after whole object has been dehydrated, so that you can do modifications on the resulting dictionary before it gets serialized.
class PostResource(ModelResource):
children = fields.ToManyField('MyApp.api.resources.PostResource',
attribute='comments', full=True, null=True)
def alter_detail_data_to_serialize(self, request, data):
return data.get('children', [])
As to different representation of the same model - yes. Basically, you shouldn't make a single Resource have many representations as that would lead to ambiguity and would be difficult to maintain.
I have a Django Form class defined likes this in Models:
class AccountDetailsForm(forms.Form):
...
adminuser = forms.ModelChoiceField(queryset=User.objects.all())
This works OK, but it has some limitations I can't seem to work around:
(1) I would like to use a filter on the queryset, based on a variable accountid passed to the form, like this:
User.objects.filter(account=accountid)
This can't work in the model because accountid can't be passed as a variable, of course.
It follows that the queryset must somehow be defined in the Views, but as far as I can see it's a required field in the Form class.
(2) I would like to make the default choice of AccountDetailsForm an object in the database, which I can select in the Views like this:
User.objects.filter(account=accountid).filter(primary_user=1)
I've tried specifying the adminuser as a default value in the form, (which works with other standard form fields, like CharField):
adminuser = User.objects.filter(account=accountid).filter(primary_user=1)
...
form = AccountDetailsForm({'adminuser': adminuser})
return render_to_response('accounts/edit/accountdetails.html',
{'form': form, 'account':account})
But no luck.
Should I be using something other than ModelChoiceField given the flexibility I need here?
Thanks.
Override the init method and accept a new keyword argument
class AccountDetailsForm(forms.Form):
...
adminuser = forms.ModelChoiceField(queryset=User.objects.all())
def __init__(self, *args, **kwargs):
accountid = kwargs.pop('accountid', None)
super(AccountDetailsForm, self).__init__(*args, **kwargs)
if accountid:
self.fields['adminuser'].queryset = User.objects.filter(account=accountid)
form = AccountDetailsForm(accountid=3)
You can always just set the choices manually in the view as well.
form = AccountDetailsForm()
form.fields['adminuser'].queryset = User.objects.filter(account=accountid)
Be warned: you are not setting default values by passing in a dictionary to a form like in your example.
You are actually creating a Bound Form, potentially triggering validation and all that jazz.
To set defaults, use the initials argument.
form = AccountDetailsForm(initial={'adminuser':'3'})
You can override the field in the view
yourForm = AccountDetailsForm()
yourForm.fields['accomodation'] = forms.ModelChoiceField(User.objects.filter(account=accountid).filter(primary_user=1))
Something that hasn't been mentioned here yet is the Form.clean() method. This method is specifically for custom validation.
For your example, you could do something like this:
class AccountDetailsForm(forms.Form):
adminuser = forms.ModelChoiceField(queryset=User.objects.all())
account_id = forms.IntegerField() # or ModelChoiceField if that applies
def clean(self):
account_id = self.cleaned_data['account_id']
self.cleaned_data['adminuser'] = User.objects.filter(account_id=account_id)
return self.cleaned_data
The clean() method gets called after the default clean methods, so you can use self.cleaned_data (same as form.cleaned_data in the view) and return it however you'd like.
Even better, you can name the method according to the field you'd like to clean (def clean_adminuser) and make easier to read.
def clean_adminuser(self):
account_id = self.cleaned_data['account_id']
return User.objects.filter(account_id=account_id)
Also in this method you can call Form.add_error() if there are any issues you want to handle.
In Django 2.0 you can pass object (User in your case) from the view to the form like this (you have to retrieve obj from the DB first):
form = AccountDetailsForm(initial={'adminuser': adminuser})
It will give you a default selected object (answers your 2) question)