Django - Get instance pk inside form widget class - django-models

I have created a custom widget which has a link to some ajax functionality. In order to get this to work, I need to pass the pk of the instance I am currently editing. It seems that widgets have no way of accessing the current model instance (for good reason!) so I was wondering how I would get this information? Would I have to obtain it from the uri or is there a handy method I am overlooking which will give me what I need.
Thanks

You can override the __init__ method of the widget and pass it the the form instance. From the form instance you can get the pk if exists.
In this answer, they bind the form_instance to the widget on the init of the form.
django - how can I access the form field from inside a custom widget
Here is another question post talking about custom widgets accessing form instances.
Country/State/City dropdown menus inside the Django admin inline

In addition to chosen answer:
To use AdvancedModelChoiceField, and still render form via regular widgets, i subclassed django widget and overrided some methods.
first method be optgroups. Replace
for index, (option_value, option_label) in enumerate(chain(self.choices)):
with
for index, (option_value, option_label, obj) in enumerate(chain(self.choices)):
Then, we need to pass that obj to option.
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None, obj=None):
option = super(RadioSelect, self).create_option(name, value, label, selected, index, subindex, attrs)
option['obj'] = obj
return option
In widget template access obj like this:
{{ widget.obj.object_attr }}
And finally, we can use our custom widget and field in our form:
class MyForm(forms.ModelForm):
myfield = AdvancedModelChoiceField(
widget=MyWidget,
queryset=MyModel.objects.all(),
empty_label=None
)

Override the model field that you will use this widget with, and set your widget as default widget. When setting the default widget, instantiate the widget and pass whatever you need.
For example, I override the ForeignKey class to add a link after the select input.
The widget:
class SelectAdd(widgets.Select):
def __init__(self, related_name, related_create_url, attrs=None):
self.related_name = related_name
self.related_create_url = related_create_url
super().__init__(attrs=attrs)
def render(self, name, value, attrs=None, **kwargs):
rendered = super(SelectAdd, self).render(name, value, attrs, **kwargs)
return rendered + mark_safe(u'''
<span>Add new {} : {}</span>
'''.format(self.related_name, self.related_create_url))
And the model field:
class ForeignKeyAdd(ForeignKey):
def formfield(self, **kwargs):
db = kwargs.pop('using', None)
if isinstance(self.remote_field.model, six.string_types):
raise ValueError("Cannot create form field for %r yet, because "
"its related model %r has not been loaded yet" %
(self.name, self.remote_field.model))
defaults = {
'form_class': forms.ModelChoiceField,
'queryset': self.remote_field.model._default_manager.using(db),
'to_field_name': self.remote_field.field_name,
'widget': SelectAdd(
related_name=self.name,
related_create_url=get_crud_url(self.related_model, 'create')
),
}
defaults.update(kwargs)
return super(ForeignKey, self).formfield(**defaults)

Related

Wagtail ModelAdmin cleaning and validating fields that depend on each other

In Django you can add a clean method to a form to validate fields that depend on each other:
def clean_recipients(self):
data = self.cleaned_data['recipients']
if "fred#example.com" not in data:
raise ValidationError("You have forgotten about Fred!")
# Always return a value to use as the new cleaned data, even if
# this method didn't change it.
return data
How can I add a custom form with a clean method to Wagtail ModelAdmin?
Wagtail has the panels concept and dynamically builds the form. I can't find anything about overriding the form. There is information about customising the create and update views. Custom views seem a bit cumbersome.
I got an answer via the Wagtail Slack form #CynthiaKiser.
The base_form_class lets you set a WagtailAdminModelForm on the model.
from wagtail.admin.forms import WagtailAdminModelForm
class Foo(models.Model):
...
base_form_class = FooForm
class FooForm(WagtailAdminModelForm): # For pages use WagtailAdminPageForm
def clean(self):
data = self.cleaned_data['recipients']
if "fred#example.com" not in data:
raise ValidationError("You have forgotten about Fred!")
return data
All of the CRUD forms you encounter in Wagtail are just Django ModelForm instances underneath, so these Django docs are relevant (thanks #ababic)
An alternative to base_form_class is to specify a clean method on the model. It will be called by any form.
from django.core.exceptions import ValidationError
class Foo(models.Model):
...
def clean(self):
if "fred#example.com" not in self.recipients:
raise ValidationError(
{'recipients': _("You have forgotten about Fred!")}
)

Simple PUT not modifying the model

The route is shown below and I can confirm the request is hitting the route, however, the model parameter is the currently saved model, when I'd expect it to be the model with updated properties.
#Page.method(request_fields=('id',),
path='page/{id}', http_method='PUT', name='page.udpate')
def PageUpdate(self, model):
if not model.from_datastore:
raise endpoints.NotFoundException('MyModel not found.')
model.put()
return model
The request_fields field specifies what comes in the request, so you'll want to include a lot more. The _message_fields_schema property (discussed in simple_get example) is best to use.
class Page(EndpointsModel):
_message_fields_schema = ('id', ... other properties)
and then just let the default be used:
#Page.method(path='page/{id}', http_method='PUT', name='page.update')
def PageUpdate(self, page):
if not page.from_datastore:
raise endpoints.NotFoundException('Page not found.')
page.put()
return page
NOTE: I also changed the spelling of 'page.udpate' and the text in the error message.

How to use hyperlinks to represent relationships instead of primary keys in Django REST framework

I want to get my object index as a "resource_uri" instead id
I take the usual way I make a model , views , serializers :
class User(BaseModel):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
models.CharField()
class UserSerailizers(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id','user','formatted_address')
classclass UserList(generics.ListCreateAPIView):
queryset = Image.objects.all()
serializer_class = UserSerializer
when i call < my_domain/user/ > I get this response
{
id:1,
name:'toto'
}
but I want to have an answer to this form:
{
'url': my_domain/user/1/
'name': 'toto'
}
Any thoughts?
If you want a hyperlink instead of a primary key in your model representations, you have to use either HyperlinkedModelSerializer or more generic Serializer along with HyperlinkedIdentityField and/or HyperlinkedRelatedField. The former is probably what you are looking for.
The HyperlinkedModelSerializer class is similar to the ModelSerializer class except that it uses hyperlinks to represent relationships, rather than primary keys.
See Django REST framework documentation for more details.
As already commented, you need to use the HyperlinkedModelSerializer as you've shown.
The lookup_field attribute should be inside the Meta class.
And the latest and this is a guess: You just have a ListView for your User model. In order to show the detail for the user, you need also the retrieve method. I would recommend you using the ModelViewset so it automatically implements all methods.

what's the best way to set computed property of app engine model in appengine after fetch from datastore?

I have my own User model in app engine, which should have a property of his gravatar url. However, since this can be very quickly computed using his email address, it doesn't make sense to store it. Is there a way to just automatically initialize this property when it s loaded from the datastore?
I could just add a method called get_avatar_url(), but you can't call an object's methods (as far as I know), from within a jinja2 template, and I don't want to post all these values individually to the template.
You can define a method, as you describe, or you can define a property, like this:
class MyModel(db.Model):
email = db.StringProperty(required=True)
#property
def avatar_url(self):
return "http://gravatar.com/avatar/%s" % (hashlib.md5(self.email).hexdigest(),)
You can then refer to this as instance.avatar_url (or in a template, {{instance.avatar_url}}).
Either will work fine in a jinja2 template, but using a property is slightly neater if you need to request it elsewhere. Since only datastore property instances result in storing data in the datastore, your property will not be stored in the datastore.
It's ok to call them from a template. All you need to do is to declare this model's method as classmethod or property
Here's a quick example:
# sample model
class UserProfile(db.Model):
...
email = db.EmailProperty()
...
#property
def id(self):
return self.key().id()
#classmethod
def get_avatar_url(self):
# whatever you need to call gravatar url
return self.email
# sample view
def show_user(user_id):
user = User.all().filter("user = ", user_id).get()
flowers = Flower.all().filter("user = ", user)
return render_template('index.html', u=user, f=flowers)
# sample template
<div class="user">user id: {{ u.id }}, and gravatar: {{ u.get_gravatar_url() }}<div>
HTH.
You most certainly can call methods within a template. That is the best way to do it.

Using Django CreateView without Form to Create Object

I am using classed based views with django 1.3 and am trying to figure out how to create an object without using the form. I do not need any user input to create the object but I am still getting an error message that the template is missing. Below is my current view where I have tried to subclass the form_valid method but its not working. Any help would be appreciated.
class ReviewerCreateView(CreateView):
model = Reviewer
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
self.object.role = 2
self.object.save()
return HttpResponseRedirect(self.get_success_url())
A CreateView is a specialized view whose purpose is to display a form on GET and validate the form data and create a new object based on the form data on POST.
Since you don't need to display a form and process the form data, a CreateView is not the tool for your job.
You either need a plain old function-based view, or, if you prefer to use a class-based view, derive from View and override get() or post(). For example, adapting your sample code:
class ReviewerCreator(View):
def get(self, request, *args, **kwargs):
Reviewer(user=request.user, role=2).save()
return HttpResponseRedirect('/your_success_url/')
I don't believe a view needs to do anything explicit with a form if it does not need one.
You can instantiate a Reviewer object. It's just a python object.
class ReviewerCreateView(CreateView):
model = Reviewer
self.object.user = self.request.user
self.object.role = 2
self.object.save()
return HttpResponseRedirect(self.get_success_url())

Resources