Django user injection in model properties - django

I have this models in Django:
News
Comments
Reactions
Relations are:
a News has various Comments
a Comment has various Reactions
The problem is the user (in request / session): the user may subscribe to a reaction, or a comment; he may be logged in or not. (it's a foo example, it doesn't have much sense)
I can't do in template:
{% for reaction in this_news.comments.reactions %}
{{ reaction.name }}
{% if reaction.user_subscribed %} #reaction.user_subscribed(request.user)...
You have subscribed this reaction!
{% endif %}
{% endfor %}
Problems are:
I can't call the method in the template with a parameter (see the comment above)
Models don't have access to request
Now i'm calling an init_user method in News Model, passing the request. Then i have the same method in Comment and Reaction model, and i have to set the user_subscribed property cycling the children of each model.
Isn't there a smarter way to do this?
EDIT: thanks to the Ignacio's hint about using custom tag i'm trying to do a generic mode to pass the user (avoiding the use of closures because i don't know how to use them atm):
def inject_user(parser, token):
try:
# split_contents() knows not to split quoted strings.
tag_name, method_injected, user = token.split_contents()
except ValueError:
raise template.TemplateSyntaxError("%r tag requires exactly three arguments" % token.contents.split()[0])
return InjectUserNode(method_injected, user)
class InjectUserNode(template.Node):
def __init__(self, method_injected, user):
self.method_injected = template.Variable(method_injected)
self.user = template.Variable(user)
def render(self, context):
try:
method_injected = self.method_injected.resolve(context)
user = self.user.resolve(context)
return method_injected(user)
except template.VariableDoesNotExist:
return ''
When i use it {% inject_user object.method_that_receives_a_user request.user %} i come to this error 'str' object is not callable in method_injected(user); how i can fix that?

Write custom template tags that take the user and set a context variable to indicate presence or absence of the criterion.

I've resolved it in a less elegant way, but it worked for me.
I've created a sort of singleton in my User defined class, with a property that i set in every view i need it.
The property is User.current.
Then, inside the models, where i need that i get the current user looking in User.current.

Related

Flask Login: User.is_anonymous() is a function, but current_user.is_anonymous is an attribute. Why?

This is my first time using flask-login, and I need to support anonymous users. Authenticated users will have special privileges. My User model implements its own version of the UserMixin functions; the relevant part is:
def is_authenticated(self):
"""Return True if the user is authenticated."""
return self.authenticated
def is_anonymous(self):
"""If you're not authenticated, you're anonymous."""
return not self.authenticated
Some templates will have code like the following:
{% if current_user is not defined or current_user.is_anonymous %}
...render HTML 1
{% else %}
...render HTML 2
{% endif %}
This works fine for anonymous users, but authenticated users will also see HTML 1 because current_user.is_anonymous evaluates to <bound method User.is_anonymous of <User *email-address*>. Meanwhile, if I change the condition to current_user.is_anonymous(), that will throw an error for anonymous users ("TypeError: 'bool' object is not callable"). My understanding is that the User Model needs is_anonymous to be a function, but its a Boolean attribute of current_user. This inconsistency is kind of irritating, but I feel like I'm missing something obvious.
What is the best way to correctly check to see if a user is anonymous?
EDIT: This is my user_loader:
#login_manager.user_loader
def load_user(user_id):
if user_id is not None:
return User.query.filter_by(email=user_id).first()
return None
EDIT 2: I removed my custom context processor bc I learned that LoginManager provides one for me. Still, the problem persists. In my templates, current_user.is_anonymous still evaluates to <bound function....
Victor is correct; I was missing the #property decorator. I knew it was something simple. Thanks Victor!

Saving object from ListView in django

I have a generic ListView which displays all objects from a model and I would like users to be able to choose one object for further processing by storing in session or in another model. What would be the best way to go about this?
views.py
class TranscriptListView(generic.ListView):
model = Transcript
template_name = 'transcript_list.html'
template
{% block content %}
<ul>
{% for transcript in transcript_list %}
<li>
{{transcript.name}}
<p>{{transcript.text}}</p>
</li>
{% endfor %}
</ul>
For selecting something to store in a session, I'd just do a
class SelectTranscriptView(SingleObjectMixin, View):
model = Transcript
def post(self, request, *args, **kwargs):
object = self.get_object()
request.session['selected_transcript'] = object.id
return redirect(...)
This view only accepts POST, since GET requests should be idempotent, i.e. they should not modify any state. Setting a session value is definitely modifying state. This means you'll need to use a form (or a JavaScript function) to POST data to the URL you hook this up to.
More importantly, though: Setting something in the session is not necessarily a good idea at all. Consider an advanced user deciding to open multiple tabs to your site, and trying to select one transcript for one tab, and another in the other tab. This won't be possible if you store the selected thing in the session! Instead, I'd design things so the ID of the object being edited or viewed is always in the URL (as happens with DetailViews and the ilk anyway).

django-rules: trouble making it work as expected in templates

Disclaimer: I'm new to django and django-rules.
I have defined my model. The Model has 2 foreign keys to the user table. Creator and supervisor.
Instances should be changeable/updated by staff, creator or supervisor.
I have defined predicates for is_creator and is_supervisor and made a rule:
#rules.predicate
def is_creator(user, mymodel):
return mymodel.creator == user
#rules.predicate
def is_supervisor(user, mymodel):
return mymodel.supervisor == user
can_edit = is_supervisor | is_creator | rules.is_staff
And in the models meta class I added:
rules_permissions = {
'change': can_edit
}
In my view I then want to show an edit button that links to edit form based on these permissions.
{% block content %}
{% has_perm 'mymodel.change_mymodel' user instance as can_edit %}
{% if can_edit %}
<button type="button" class="btn btn-warning"><h6>Edit</h6></button>
{% endif %}
{% endblock %}
When I log in as superuser, the button is displayed as expected.
When I user a test user that should be able to edit a specific instance, the button is not displayed at all. So there are certain checks made but not as expected.
I have a second similar functionality for the index page. Showing only actions the users has the privilege for. Again, the superuser sees it all but the test user does not.
In this case I have below predicate which is used as "add" permission" on a different Model:
#rules.predicate
def can_add_xyz(user):
return rules.is_staff | rules.is_group_member("Add_XYZ")
It seems in both cases all the checks besides the is_staff seem to fail. What am I doing wrong?
There are two issues going on. First RTFM and do it correctly:
In my template I used has_perm entity_name.add_entity_nameinstead of using as properly described in the documentation has_perm myapp.add_entity_name.
After fixing that the predicates now actually got called and I could debug them. And there a bigger problem revealed itself. I was able to fix it but don't like the fix.
The predicate:
#rules.predicate
def is_creator(user, mymodel):
return mymodel.creator == user
The issue is that in the template I'm checking this permission, mymodel is a related entity generated by a nested django-rest-framework serializer. This means that the instance used in the template that is then submitted to this predicate is not a mymodel instance but an OrderedDict and hence I get an Exception like OrderedDict has no attribute 'creator'.
An additional issue is that creator isn't directly a Django auth_user but an OneToOne extended User. So mymodel.creator == user would never be true.
#rules.predicate
def is_creator(user, mymodel):
#if called from template as related entity,
#mymodel is an OrderedDict from a serializer
if isinstance(mymodel, collections.OrderedDict):
return mymodel["creator"] == user.userprofile.pk
return mymodel.creator == user.userprofile
This fixes the issue and now the right things are displayed but I'm not entirely happy with this (type checking). So any advice to make this better is still welcome.

how to pre-fill an HiddenInput in admin ModelForm

I'm sure that problems similar to the one I'm going to ask about were already discussed somewhere, but I always found very tricky solutions while I think there must be a very straightforward one (you can guess I'm a totally newbie).
I have a model (Lecturer) that is connected OneToOne to the User model:
class Lecturer(models.Model):
user = models.OneToOneField(User)
... other fiels follow ...
I'd like each user to create its own Lecturer object through the admin site.
My idea is to present to the user the add_view without the user field. Then I would something like obj.user=request.user when saving the model.
In other words, I don't want to give the user the possibility of selecting a different user among the registered for its own Lecturer object.
I modified the form by overriding the get_form method and by providing a custom form:
admin.py
class LecturerAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
if is_lecturer(request.user):
kwargs['form'] = UserLecturerForm
return super(LecturerAdmin, self).get_form(request, obj, **kwargs)
class UserLecturerForm(forms.ModelForm):
class Meta:
model = Lecturer
fields = ('__all__')
widgets = {'user': forms.HiddenInput()}
I cannot just exclude the user field and give it a value at some other level (e.g. save_model or clean ...) because this raises an error at the template rendering level:
Django Version: 1.7.7
Exception Type: KeyError
Exception Value: u"Key 'user' not found in 'LecturerForm'"
Exception Location: /usr/lib/python2.7/dist-packages/django/forms/forms.py in getitem, line 147
Python Executable: /usr/bin/python
Python Version: 2.7.9
Error during template rendering
In template /usr/lib/python2.7/dist-packages/django/contrib/admin/templates/admin/includes/fieldset.html, error at line 7
[...]
7 <div class="form-row{% if line.fields|length_is:'1' and line.errors %} errors{% endif %}{% if not line.has_visible_field %} hidden{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}">
I tried making the user field hidden in the form. But then the problem came of how to give it the correct request.user value, given that the form doesn't know anything about the request. If I don't fill it with a valid value, the form will not validate (and I cannot use solutions involving the save_model as suggested here)
I found solutions that involve changing the view, but I wouldn't do it in the admin context.
An alternative would be to change the validation behavior, but still validation is a method of the form and the form doesn't know request.
Any help is appreciated.

How do I write a Django template tag for access control?

I'm trying, in vain, to create a simple Django template tag to either show or hide a "delete" link next to a submitted comment on my site.
In a nutshell, I want to pass the comment object to the template tag, determine if the currently logged in user is authorized to delete the comment and then either show or not show the link.
The usage in my template would be like so:
{% load access_tags %}
{% if_authorized comment %}
Delete
{% endif_authorized %}
Rest assured that I also check in the appropriate view if the user is authorized to delete the comment.
Does this type of tag have a specific name? It would certainly help me with my Google searches if it did. Thanks for your help!
UPDATE 1:
The way my site works, two people are potentially authorized to delete a comment: 1) the comment creator and 2) the owner of the post where the comment was left. Because of this, I need to determine, per comment, if one of those conditions is present.
I don't think I can use something like Django's built-in permission sytem, since it requires that permissions "be set globally per type of object, no per specific object instance".
In my case, user "Bob" may have permissions to delete a comment (if he wrote it or it is on a post he created), but he also may not be allowed to delete it (if he is looking at a comment on someone else's post).
UPDATE 2:
It appears that you can't pass objects to a template tag, only strings: "Although you can pass any number of arguments to a template tag using token.split_contents(), the arguments are all unpacked as string literals." I guess I'll pass the id of the comment object in question and pull it in the tag.
I was wrong about this, just have to access the passed in object like:
self.comment.resolve(context).user
vs.
self.comment.user
OK, this is how I did it...
The tag is used like this in the template:
{% load access_tags %}
{% if_authorized comment.user object.user user %}
Delete
{% endif_authorized %}
The template tag file is called "access_tag.py" and is in my app's "templatetags" directory. This is the contents of "access_tag.py":
from django.template import Node, NodeList, TemplateSyntaxError
from django.template import Library, Variable, VariableDoesNotExist
register = Library()
def do_if_authorized(parser, token):
"""
Outputs the contents of the block if the 'comment owner' or the
'page owner' is also the 'authenticated user'. As well, you can use
an {% else %} tag to show text if the match fails.
Takes three parameters:
1) the comment owner
2) the page owner
3) the current authenticated user
"""
bits = token.contents.split()
if len(bits) != 4:
raise TemplateSyntaxError("%s tag takes three arguments: \
1) the comment owner \
2) the page owner \
3) the current authenticated user" % bits[0])
nodelist_true = parser.parse(('else', 'endif_authorized'))
token = parser.next_token()
if token.contents == 'else':
nodelist_false = parser.parse(('endif_authorized',))
parser.delete_first_token()
else:
nodelist_false = NodeList()
return IfAuthorizedNode(bits[1], bits[2], bits[3], nodelist_true, nodelist_false)
class IfAuthorizedNode(Node):
def __init__(self, comment_owner, page_owner, authenticated_user, nodelist_true, nodelist_false):
self.nodelist_true = nodelist_true
self.nodelist_false = nodelist_false
self.comment_owner = Variable(comment_owner)
self.page_owner = Variable(page_owner)
self.authenticated_user = Variable(authenticated_user)
def render(self, context):
try:
comment_owner = self.comment_owner.resolve(context)
page_owner = self.page_owner.resolve(context)
authenticated_user = self.authenticated_user.resolve(context)
except VariableDoesNotExist:
return ''
if comment_owner == authenticated_user or page_owner == authenticated_user:
return self.nodelist_true.render(context)
else:
return self.nodelist_false.render(context)
register.tag('if_authorized', do_if_authorized)
Done. In the end, it would have been pretty easy to just use the built-in {% if %} tag to do this comparison, but since I'll have other per-object authorizations to do, I will continue to build out these custom "access_tags". Plus, the template code looks so much tidier :)
There already exists a project that aims to do what you would like to do.
django-authority allows for fine grain control over permissions in templates.
Django 1.2 also contains user permissions in templates, too.
how about this... create a custom tag that writes a variable in the context, then test that variable using {% if %}
it'd be something like this:
{% check_access comment %}
{% if has_access %}
Delete
{% endif %}
of course the "check_access" tag would write the "has_access" in the context.
Good Luck