I'm using the pyfacebook module for Django to implement facebook Connect for one of my sites, I'm not using a local User object because I only want to use Facebook for login.
I understand that I can't access the request object from the save method on a Django form, so how would I access the facebook object that the provided middleware gives you?
I want to attach the users facebook UID to an object being created from a form, I could add it as a hidden field to the form but then people could just change it to make it appear that a question came from someone else.
What is the best way to pass in this uid to the save() method?
The correct way to do this is to use an instance of the object you're trying to save, like this:
question = Question(user=int(request.facebook.uid))
form = QuestionForm(request.POST, instance=question)
question = form.save()
question.put()
Do this in your view, NOT in the save() method of your object.
Watch out if any of the fields are required though, you'll have to specify them in the instance objector calling form.save will throw an exception.
You can set a variable on the form when you create it.
views.py
def myview(request):
form = FacebookConnectForm(request)
forms.py
class FacebookConnectForm(forms.Form):
def __init__(self, instance):
self.instance = instance
def save(self):
print self.instance
...
Related
I have a FormView (currently a WizardView as I have multiple forms) which takes input from the (anonymous) user and passes the cleaned data to be used in a request to an external api.
However I'm not clear how best to pass the data recieved from one view to the next view?
The data received from the api is to be displayed in the next view.
(If the data was held internally (rather than in an external api) I would probably try a ListView with the relevant parameters passed from the FormView to the ListView via a session)
However the api returned data will have many pages of data.
What's the best way to manage this in django? (This doesn't need to be async as the returned data is required before the user can proceed.)
class QuotesWizard(SessionWizardView):
def get_template_names(self):
return [QUOTES_TEMPLATES[self.steps.current]]
def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated():
return redirect(settings.LOGIN_REDIRECT_URL)
return super().dispatch(request, *args, **kwargs)
def done(self, form_list, form_dict, **kwargs):
postcode = form_dict['postcode'].cleaned_data['postcode']
service = form_dict['service'].cleaned_data['service']
usage = form_dict['usage'].cleaned_data['usage']
usage = convert_usage(usage)
# get data from external api
data = get_quotes(postcode, usage_ofgem, service_type)
# redirect to quotes page - how to manage transfer of data?
return redirect('quotes')
I have a similar approach in a module, also not surprisingly with address data.
I've chosen to store the results in the database, so I've created a model for it. One reason is that number of calls have a price, so each call I'm not making, I'm saving money. Another is that I'm not bound by the whims of the remote API: My API is consistent for the browser and Django views and if something changes remotely, I have to fix it only in one place.
The crux is to make a QuerySet in which you override the get method and in your case also the filter method in a similar way:
class PostcodeApiQuerySet(QuerySet):
# fields, other methods
def get(self, *args, **kwargs):
try:
return super(PostcodeApiQuerySet, self).get(*args, **kwargs)
except self.model.DoesNotExist:
self._validate_lookup_kwargs(kwargs)
pc = self._get_field_lookup_value('pc', kwargs)
house_nr = self._get_field_lookup_value('house_nr', kwargs)
house_nr_add = self._get_field_lookup_value('house_nr_add', kwargs)
try:
address = self._fetch_one(
pc, house_nr, house_nr_add=house_nr_add
)
except ObjectDoesNotExist as e:
raise self.model.DoesNotExist(e.args[0])
except KeyError as e:
raise self.model.DoesNotExist(e.args[0])
except ValueError as e:
raise self.model.DoesNotExist(e.args[0])
street = self._handle_foreign_for_address(address)
instance = self.model(street=street, **address)
instance.full_clean()
instance.save()
instance.refresh_from_db()
return instance
The methods called here that are not included, deal with the remote API and mapping of fields to API filters, but it should give you a general idea. The filter method will be a bit more complex, as you will have to figure out what you don't have and as such what you need to fetch remotely.
Once you've got this done, you can reduce your pages of data to a list of ID's, which should easily fit in a session. Then you can use ListView and override get_queryset() to teach it about the list of ID's to get from the session.
EDIT
So the flow is:
FormView (GET): render selection form
FormView (POST), in form_valid: Apply filters to overridden filter method of the custom queryset (Also see Manager.from_queryset) and store ID's in session
FormView (POST), in form_valid creates redirect to success_url
ListView (GET) invokes get_queryset which you've taught to deal with ID's in session
Good luck!
There are multiple ways to accomplish this:
Pass the data using GET params while redirecting. This way the resultant view would be cacheable and stateless. (recommended)
Use django sessions. Add the variable to session, which can be
retrived globally. Sessions act as global variables, so use them
only as last resort.
Use django messages if you want to store variables in session only for next view.
I have a CRUD in django rest framework and to control the permissions I'm using permission classes. Here is the permission class I created for the CRUD. This works well for update and delete:
class LinkAssetPermission(permissions.BasePermission):
message = 'Link access not allowed.'
def has_object_permission(self, request, view, obj):
return obj.content.delivery.owner == request.user and obj.content.delivery.can_change
My Problem if that this class does not work well for my create view:
class AssetCreate(generics.CreateAPIView):
"""
Link a new asset
"""
queryset = Asset.objects.none()
serializer_class = AssetSerializer
permission_classes = (permissions.DjangoModelPermissions, LinkAssetPermission, )
When obj.content.delivery.can_change returns false I cannot create, update or delete model objects. The same for obj.content.delivery.owner == request.user . I can only create, update or delete when the user is the owner. All this works as I want for update and delete, just for create that it is not working. Probably because I don't have the object yet, that's the core of my question.
The object exists when creating and I have also tried to just return False at has_object_permission and I still can create the record.
Should I create a new permission class for the insert or should I change something in my view?
Thanks for any help
I found out what I had to do. Here is my new permission class:
class LinkAssetPermission(permissions.BasePermission):
message = 'Link access not allowed.'
def has_permission(self, request, view):
try:
content = Content.objects.get(pk=request.parser_context["kwargs"]["content_id"])
except:
return False
return content.delivery.owner == request.user and content.delivery.can_change
def has_object_permission(self, request, view, obj):
return obj.content.delivery.owner == request.user and obj.content.delivery.can_change
On create the method has_object_permission is not considered but the has_permission is. So I retrieve the parameter sent from the urls.py, query and check the properties of the object I want.
Thanks
has_object_permissions() gets called whenever .get_object() method is called in a generic view i.e. a particular resource is being accessed.
Now, all the detail requests (retrieve, update, delete) access a particular resource/object, it works. For create requests, get_object() method does not get called, so it does not work in your case.
For handling create requests, you can create another serializer where there is validations for the above 2 conditions. And then change your permission classes to work for detail requests only.
I'm building a job application form. A logged-in user is permitted to apply to the job only once (there's only one job). At the moment, a user is able to directly access the job application (FormView), by typing in its specific URL, and an error message is thrown AFTER the user submits the form. To achieve this, I'm doing a couple of things:
(1) Wrapping the job application view in login_required()
(2) Using the form_valid() method to check whether or not a user has already submitted an application to this specific job via:
def form_valid(self, form):
form = form.save(commit=False)
form.user = self.request.user
if Application.objects.filter(user=user_id):
messages.error(self.request, 'Sorry, our records show that you have already applied to this job.')
return redirect('/')
But I'd rather not permit them to reach the page at all. Instead, I want to check whether or not a user has already applied (during the request), and redirect them away from the form, if they have already applied. I have limited access to logged-in users that pass a test in the past, using something like:
def job_application_view(request):
active_user = request.user
if Application.objects.filter(user=active_user):
return HttpResponse("Some response.")
However, I can't seem to figure out how to access request via the FormView Class-Based View. I'm sure I'm missing something simple. Perhaps another method of FormView I'm missing?
You can still use decorators on class-based views, but they are slightly more difficult to apply than function-based views.
class ApplicationView(FormView):
# ...
#method_decorator(user_passes_test(job_application_view))
def dispatch(self, *args, **kwargs):
return super(ApplicationView, self).dispatch(*args, **kwargs)
Answering specific parts of your post...
I have limited access to logged-in users that pass a test in the past
With class-based views, you need to decorate the url or decorate the dispatch method with any decorators you are interested in applying.
However, I can't seem to figure out how to access request via the FormView Class-Based View. I'm sure I'm missing something simple. Perhaps another method of FormView I'm missing?
You can access the request with self.request
I want to write a Mixin (or use a third-party one) for checking if the logged user is the owner of certain object.
url(r'^api/mymodel/(?P<pk>\d)/?', CreateUpdateMyModel.as_view(), name='create_or_update')
class MyModel(models.Model):
owner = models.OneToOneField('auth.User')
class OwnerRequired(SingleObjectMixin):
# do this self.object = self.get_object() for getting the object
#
# some logic for checking if request.user == self.object.owner
# otherwise return something like Response(status=status.HTTP_403_FORBIDDEN)
Inheriting from SingleObjectMixin is important to me, since I want to be able to do something like:
class CreateUpdateMyModel(APIView, OwnerRequired):
model = MyModel
def post(self, request, *args, **kwargs):
# self.object should be available here
# so that write some code taking it into account
How OwnerRequired should look for fulfilling this?
I'm open to another alternatives, in fact, I've checked PermissionRequiredMixin from django-braces and I'd like to use it, but I'm not sure how to do it
permission_required = ?? # I can code a method for, but how can I pass the model instance and the request.user?
Is there another simple alternative?
Take a look at object level permissions. There's also a relevant example on that page in the examples sections - see the IsOwnerOrReadOnly example.
Also note that object level permissions are only run either:
You are using the GenericAPIView or a subclass of it, and calling get_object() to retrieve the instance.
You explicitly call self.check_object_permissions(request, instance within your view code.
How can I get current logged user profile in django, into models ?
I have this code:
def save(self, *args, **kwargs):
profile = User.get_profile()
self.empresa_id = profile.idempresa
self.empresa_id = profile.id_comerx3c
super(Cliente, self).save(*args, **kwargs)
but doesn`t work..
If you're using a ModelForm, you have three choices:
Set the profile when you instantiate the form
Create the object instance in a view, but not commit it, then set the profile property and
commit the save
Pass the profile to the save method, and set the property before
calling super
If you're just dealing with a Model, you have two choices:
Pass the profile in when you instantiate the object
Set the profile on an instance of your object before you call save