I've been using django-relationships to allow users to follow each other. If Bob follows Joe. Bob will be able to see all of Joe's photos. However if Bob blocks John, John will not be Bob's photos.
My problem is that I don't know how to restrict content from a blocked user. I've seen the examples but I still can't seem to find a solution.
Assume Photographer is a FK to User
Here is my FollowingPhoto resource (this resources returns all photos that belong to the people that the user is following):
FollowingPhoto(ModelResource):
photographer = fields.ForeignKey(PhotographerResource, 'photographer', full=True)
class Meta:
queryset = Photo.objects.all().order_by('-postTime')
resource_name = 'following'
fields = ['id', 'title', 'url', 'likes','postTime','photographer', 'location_id', 'location_name']
authentication = BasicAuthentication()
authorization = DjangoAuthorization()
serializer = Serializer(formats=['json'])
include_resource_uri = False
filtering = {
'postTime': ALL,
'photographer' : ALL_WITH_RELATIONS,
}
def get_object_list(self, request):
return super(FollowingPhoto, self).get_object_list(request).filter(photographer__user__in = request.user.relationships.following())
Now as you may have noticed with my get_object_list, it returns all the content from the users I am following. How can prevent users who are blocked from showing up on this list?
The Django-relationships app generates two tables in postgresql, the table below is the relationships_relationships table:
id from_user to_user_id status_id created
[PK] serial integer integer timestamp
6 1 5 1 2012-10-05 20:10:29.848667+00"
7 1 3 1 2012-10-05 20:11:23.319961+00"
And the other table is the relationships_relationshipstatus table:
id name verb from_slug login_required private
[PK] serial character character character varying boolean boolean
1 Following follow friends FALSE FALSE
2 Blocking block ! TRUE TRUE
Below I have added a link to Django-relationships models.py so that you may get further clarification:
models.py
It looks to me like you just need a .exclude clause - something like:
def get_object_list(self, request):
return super(FollowingPhoto, self).get_object_list(request).filter(photographer__user__in = request.user.relationships.following()).exclude(photographer__user__in=request.user.relationships.blocking())
That will prevent Bob from seeing John's photos if Bob is blocking John. Given that Bob only sees John's photos if Bob is following John, it will do nothing unless Bob is both following and blocking John, which seems odd to me - do you maybe want Bob to not see John's photos if John is blocking Bob instead? If so:
def get_object_list(self, request):
return super(FollowingPhoto, self).get_object_list(request).filter(photographer__user__in = request.user.relationships.following()).exclude(photographer__user__in=request.user.relationships.blockers())
Create a custom manager. Depending on whether you are selecting all photos that a user can see or all users whose photos you are allowed to see makes a difference on whether you want a RelationshipManager or PhotosManager. I'm assuming you want a feed of photos - you can adapt this to limit users pretty easily w/ a bit of sql.
PhotosManager(models.Manager):
def get_unblocked(self, user):
sql = """SELECT myapp_photo.*
FROM myapp_photo
INNER JOIN auth_user
ON myapp_photo.user_id = auth_user.id
WHERE auth_user.id != %d /*DO NOT SHOW YOUR OWN PHOTOS*/
AND myapp_photo.user_id NOT IN(
SELECT myapp_blocked.blocked_user_id
FROM myapp_blocked
WHERE myapp_blocked.user_id = %d
)""" % (user.pk, user.pk)
return self.model.objects.raw(sql)
Apologies for the horrible sql, I don't know your table structure I'm just trying to illustrate a point
Then in your Photo model add objects = PhotosManager()
And you can now get a list of photos that are not blocked in your view:
Photos.objects.get_unblocked(request.user)
EDIT:
I just realized my code only takes into account blocked users and not followers. Again w/o knowing your table structure I'd just be guessing. You can add another AND myapp_photo.user_id IN( clause to allow only photos from users that are following. Also, you could write better sql for sure, this was a quick and dirty example. my solution: write a manager, the actual provided sql is just for illustrative purposes.
Related
I got two models:
class ContactGroup(models.Model):
name = models.CharField(max_length=40)
class Meta:
permissions=(('view_group_contacts', 'View contacts from group'))
class Contact(models.Model):
name = models.CharField(max_length=40)
group = models.ForeignKey(ContactGroup)
class Meta:
permissions=(('view_contact', 'View contact'))
How can I make django guardian consider ContactGroup permissions when I'm for example doing `get_objects_for_user(User, 'appname.view_contact) but still retain option for changing permission on individual Contact?(not for excluding, only to give permission to view single contact when user don't have the permission for whole group)
Sorry, such behaviour is not supported by django-guardian. As for has_perm - it would be extremely inefficient to use it for querysets as we would need to perform >=1 query for each row in a table.
You could however perform get_objects_for_user firstly for ContactGroup, then for Contact and extend last queryset with results from the first one. Something like:
contact_groups = get_objects_for_user(user, 'appname.view_group_contacts', ContactGroup)
contacts = get_objects_for_user(user, 'appname.view_contact', Contact)
There is still problem of merging those but well, it's possible.
Very ugly workaround, it does not take for account changes in individual objects (it resets all permission to ContactGroup permissions if remove= False). But It can be rewritten to preserve changes if needed. I plan to attach it to "Sync Permissions with group" button so it will be fired only at user request. Main pro is its working with get_objects_for_user as intended.
def syncPerms(source, remove=False):
if not isinstance(source, ContactGroup):
return False
contacts= source.client_set.all()
user_perms=get_users_with_perms(source, attach_perms=True)
for contact in contacts:
for user, perm in user_perms.iteritems():
if u'view_group_contacts' in perm:
assign_perm('view_contact', user,client)
else:
if remove:
remove_perm('view_contact', user, client)
I am trying to workout how / the best, most secure way to keep a user's data separate within a django site that I need to write.
Here is an example of what I need to do...
example app ToDoList
Using django contrib.auth to manage users / passwords etc, I will have the following users
tom
jim
lee
There will be a ToDo model (in my real app there will be additional models)
class ToDo(models.Model):
user = models.ForeignKey(User)
description = models.CharField(max_length=20)
details = models.CharField(max_length=50)
created = models.DateTimeField('created on')
The issue that I am having - and may be over thinking this: How would this be locked down so tom can only see Tom's todo list, lee can only see his todo list and so on...
I have seen a few posts stating that you could use filter in every query, or use urls, so the url could look like www.domain.com/username/todo
But either way I am not sure if this is the right way / best way, or bonkers in terms of stopping users seeing each others data
cheers
Richard
One approach is to filter the ToDo items by the currently logged in user:
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from your_app.models import ToDo
#login_required
def todos_for_user(request):
todos = ToDo.objects.filter(user=request.user)
return render(request, 'todos/index.html', {'todos' : todos})
This locks down the view for authenticated users only, and filtering by the logged in user from the request, another user, even if logged in, can't access another user's ToDo records. Hope that helps you out.
Make url like www.domain.com/username/todo is one way to implement it, but it doesn't guarantee you achieve security.
What you should do keep your user's login information in a session data after user login, and every time you check certain view,
check whether that particular user has right to see this view.
using user's login info (ID, or username) when querying user's Todo list.
And I guess this link will help you to do your job.
Sessions, Users, and Registration.
I have a Friendship model with two related user objects associated with it. I would like to write a method that takes a user and returns a list of that user's friends. I was doing it via:
friends = Friendship.objects.filter(Q(user1=user) | Q(user2=user))
friends_list = [f.user1 if user == f.user2 else f.user2 for f in friends]
but this incurs a query for every user that is returned in the query set (e.g. hundreds of queries). I could write it via:
friends = Friendship.objects.select_related().filter(Q(user1=user) | Q(user2=user))
friends_list = [f.user1 if user == f.user2 else f.user2 for f in friends]
but this does an INNER JOIN on the user table. I could also write it via custom SQL,
friends = User.objects.raw("""
SELECT * FROM usertable WHERE id IN (SELECT
user1_id FROM friendstable WHERE user2_id=%d) OR id IN
(SELECT user2_id FROM lists_friendship WHERE user1_id=%d);
""" % (user.pk, user.pk))
but I was thinking there must a way to do it inside the ORM without all those extra lookups. I was trying to do something with id__in, but I couldn't find a way to get the user ids out of the friends query set.
Assuming you set up your Friendship model similar to the example below and use friend_set as the related name of the from_friend field in your model, getting a list of a users friends can be as simple as a basic list comprehension:
friends = [friendship.to_friend for friendship in user.friend_set.all()]
This would even allow you to access a user's friends in your template without having to pass that as a variable:
{% for friendship in user.friend_set.all %}
{{ friendship.to_friend.username }}
{% endfor %}
Your Friendship model would look like this:
class Friendship(models.Model):
from_friend = models.ForeignKey(
User, related_name='friend_set'
)
to_friend = models.ForeignKey(
User, related_name='to_friend_set'
)
def __unicode__(self):
return u'%s, %s' % (
self.from_friend.username,
self.to_friend.username
)
class Meta:
unique_together = (('to_friend', 'from_friend'), )
Here's a great article on building friend networking in Django:
http://www.packtpub.com/article/building-friend-networks-with-django-1.0
Note that you shouldn't have to check both from_friend and to_friend to get a users friend list. If you have a following/follower friendship model you only need all friendship instances with a from_friend = user, and if you have a double opt-in friendship model you could follow what Facebook does and add an instance for the invited friend as the from_friend once they accept the friend invite.
Django 1.2.5
Python: 2.5.5
My admin list of a sports model has just gone really slow (5 minutes for 400 records). It was returning in a second or so until we got 400 games, 50 odd teams and 2 sports.
I have fixed it in an awful way so I'd like to see if anyone has seen this before. My app looks like this:
models:
Sport( models.Model )
name
Venue( models.Model )
name
Team( models.Model )
name
Fixture( models.Model )
date
sport = models.ForeignKey(Sport)
venue = models.ForeignKey(Venue)
TeamFixture( Fixture )
team1 = models.ForeignKey(Team, related_name="Team 1")
team2 = models.ForeignKey(Team, related_name="Team 2")
admin:
TeamFixture_ModelAdmin (ModelAdmin)
list_display = ('date','sport','venue','team1','team2',)
If I remove any foreign keys from list_display then it's quick. As soon as I add any foreign key then slow.
I fixed it by using non foreign keys but calculating them in the model init so this works:
models:
TeamFixture( Fixture )
team1 = models.ForeignKey(Team, related_name="Team 1")
team2 = models.ForeignKey(Team, related_name="Team 2")
sport_name = ""
venue_name = ""
team1_name = ""
team2_name = ""
def __init__(self, *args, **kwargs):
super(TeamFixture, self).__init__(*args, **kwargs)
self.sport_name = self.sport.name
self.venue_name = self.venue.name
self.team1_name = self.team1.name
self.team2_name = self.team2.name
admin:
TeamFixture_ModelAdmin (ModelAdmin)
list_display = ('date','sport_name','venue_name','team1_name','team2_name',)
Administration for all other models are fine with several thousand records at the moment and all views in the actual site is functioning fine.
It's driving me crazy. list_select_related is set to True, however adding a foreign key to User in the list_display generates one query per row in the admin, which makes the listing slow. Select_related is True, so the Django admin shouldn't call this query on each row.
What is going on ?
The first thing I would look for, are the database calls. If you shouldn't have done that already, install django-debug-toolbar. That awesome tool lets you inspect all sql queries done for the current request. I assume there are lots of them. If you look at them, you will know where to look for the problem.
One problem I myself have run into: When the __unicode__ method of a model uses a foreign key, that leads to one database hit per instance. I know of two ways to overcome this problem:
use select_related, which usually is your best bet.
make your __unicode__ return a static string and override the save method to update this string accordingly.
This is a very old problem with django admin and foreign keys. What happens here is that whenever you try to load an object it tries to get all the objects of that foreign key. So lets say you are trying to load a fixture with a some teams (say the number of teams is about 100), its going to keep on including all the 100 teams in one go. You can try to optimize them by using something called as raw_fields. What this would do is instead of having to calling everything at once, it will limit the number of calls and make sure that the call is only made when an event is triggered (i.e. when you are selecting a team).
If that seems a bit like a UI mess you can try using this class:
"""
For Raw_id_field to optimize django performance for many to many fields
"""
class RawIdWidget(ManyToManyRawIdWidget):
def label_for_value(self, value):
values = value.split(',')
str_values = []
key = self.rel.get_related_field().name
for v in values:
try:
obj = self.rel.to._default_manager.using(self.db).get(**{key: v})
x = smart_unicode(obj)
change_url = reverse(
"admin:%s_%s_change" % (obj._meta.app_label, obj._meta.object_name.lower()),
args=(obj.pk,)
)
str_values += ['<strong>%s</strong>' % (change_url, escape(x))]
except self.rel.to.DoesNotExist:
str_values += [u'No input or index in the db']
return u', '.join(str_values)
class ImproveRawId(admin.ModelAdmin):
raw_id_fields = ('created_by', 'updated_by')
def formfield_for_dbfield(self, db_field, **kwargs):
if db_field.name in self.raw_id_fields:
kwargs.pop("request", None)
type = db_field.rel.__class__.__name__
kwargs['widget'] = RawIdWidget(db_field.rel, site)
return db_field.formfield(**kwargs)
return super(ImproveRawId, self).formfield_for_dbfield(db_field, **kwargs)
Just make sure that you inherit the class properly. I am guessing something like TeamFixture_ModelAdmin (ImproveRawIdFieldsForm). This will most likely give you a pretty cool performance boost in your django admin.
I fixed my problem by setting list_select_related to the list of related model fields instead of just True
I am trying to understand the 1-to-many relationships in datastore; but I fail to understand how query and update the record of a user when the model includes ReferenceProperty. Say I have this model:
class User(db.Model):
userEmail = db.StringProperty()
userScore = db.IntegerProperty(default=0)
class Comment(db.Model):
user = db.ReferenceProperty(User, collection_name="comments")
comment = db.StringProperty()
class Venue(db.Model):
user = db.ReferenceProperty(User, collection_name="venues")
venue = db.StringProperty()
If I understand correctly, the same user, uniquely identified by userEmail can have many comments and may be associated with many venues (restaurants etc.)
Now, let's say the user az#example.com is already in the database and he submits a new entry.
Based on this answer I do something like:
q = User.all()
q.filter("userEmail =", az#example.com)
results = q.fetch(1)
newEntry = results[0]
But I am not clear what this does! What I want to do is to update comment and venue fields which are under class Comment and class Venue.
Can you help me understand how this works? Thanks.
The snippet you posted is doing this (see comments):
q = User.all() # prepare User table for querying
q.filter("userEmail =", "az#example.com") # apply filter, email lookup
- this is a simple where clause
results = q.fetch(1) # execute the query, apply limit 1
the_user = results[0] # the results is a list of objects, grab the first one
After this code the_user will be an object that corresponds to the user record with email "az#example.com". Seing you've set up your reference properties, you can access its comments and venues with the_user.comments and the_user.venues. Some venue of these can be modified, say like this:
some_venue = the_user.venues[0] # the first from the list
some_venue.venue = 'At DC. square'
db.put(some_venue) # the entry will be updated
I suggest that you make a general sweep of the gae documentation that has very good examples, you will find it very helpful:
http://code.google.com/appengine/docs/python/overview.html
** UPDATE **: For adding new venue to user, simply create new venue and assign the queried user object as the venue's user attribute:
new_venue = Venue(venue='Jeferson memorial', user=the_user) # careful with the quoting
db.put(new_venue)
To get all Comments for a given user, filter the user property using the key of the user:
comments = Comment.all().filter("user =", user.key()).fetch(50)
So you could first lookup the user by the email, and then search comments or venues using its key.