Django: How can I let the user define semantic relations between objects? - django

In mediawiki, a user can link to a page by just entering the [[Name of the Page]]. In semantic mediawiki, a user can make a semantic relationship to a page by identifying [[example::Some Page]] as an example page.
I want to be able to do this with django models.
For example, I want a user to be able to write a description for a Task object in a workflow app, and put something like "Follow up with [[User.id:43]]."
I'd also love to be able to do the semantic thing.
Finally, I'd like to be able to look at User 43 and see what models link to it.
Is there a django package that will do some or all of this? If not, what is this methodology called generally?

First of all, I'd set up the following to handle link detection and creation:
Create a ModelForm to receive the user's submitted Task. Create a custom validator to operate on the description field of this form. This validator would check that any links entered by the user correspond to the the [[:]] format you've specified.
Override the save method on the Task model. This is a good time to inspect the contents of the description and create links as appropriate.
Side note: I would wrap the view function handling this request in a #transaction.commit_on_success decorator so that either the model and all the links get created, or nothing at all gets created.
Now, in the save method, the real action is happening. I would use a regex to pull out all the links and then work through them one by one.
For each link, you need to determine the model linked to. This would be easy with a dictionary of models keyed on their names.
You need to determine if the link has actually identified a valid model instance. If not, either skip this link or raise an exception and eject from the whole process (see above regarding transactions).
You need to create the links. See below.
Django has a standard mechanism for generic foreign keys which you should definitely consider using here. You could create a link class something like:
class Link(models.Model):
# link to particular task:
task = models.ForeignKey(Task)
# these three fields together set up a generic foreign key which
# represents the object you're linking to:
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
Your Task model objects would then automatically get an attribute called link_set which would be a list of Link instances.
Creating a link would look something like:
# encountered a description with [[User:43]]
instance = User.objects.get(pk=43)
link = Link.objects.create(task=my_task_object, content_object=instance)
By giving Link's content_object attribute an instance of another model, its content_type and object_id fields are automatically filled in for you, and your link will resolve back to that specified instance.
Hope this is helpful. Ask for more detail if you need it.

Related

How to know the user who created a specific item in Django?

Is it possible to get the id of the user who created a specific item, no matter what it, in Django? I have a site where when the users are authenticated can access a form and submit a new item. Can I retrieve who created what without adding an extra 'submitted by'
Event.objects.filter(owner=self.kwargs['pk'])
(which gives me name 'self' is not defined )?
You cannot access the information without adding a new field, but you can have a look at this library django-audit-log for easy tracking. It will add created_by and modified_by fields and maintain them.

Dynamically created proxy models do not have listed permissions on admin site

Here is my models.py:
class Item(models.Model):
# ... some irrelevent fields ...
tags = models.ManyToManyField('Tag')
class Tag(models.Model):
name = models.CharField(max_lenght=30)
category_id = models.IntegerField()
Tag is actually a general-purpose name. Each item has many different type of tags - currently there are four types: team tags, subject tags, admin tags and special tags. eventually there will probably be a few more.
The idea is, they all have basically the same fields, so instead of having like 4 tables with manytomany relationship, and instead of adding a new column for Item whenever adding a new type, everything is called a 'tag' and it's very easy to add new types without any change to the schema.
Now to handle this in the admin.py I'm using dyamically created proxy models (based on this), as such:
def create_modeladmin(modeladmin, model, name = None):
class Meta:
proxy = True
app_label = model._meta.app_label
attrs = {'__module__': '', 'Meta': Meta}
newmodel = type(name, (model,), attrs)
admin.site.register(newmodel, modeladmin)
return modeladmin
class TagAdmin(models.Model):
def queryset(self):
return self.model.objects.filter(category_id = self.cid)
class TeamAdmin(TagAdmin):
cid = 1
class SubjectAdmin(TagAdmin):
cid = 2
# ... and so on ...
create_modeladmin(TeamAdmin, name='Teams', model=Tag)
create_modeladmin(SubjectAdmin, name='Subject', model=Tag)
#... and so on ...
This works great for me. However, different staff members need different editing permissions - one guy shouldn't access admin tags, while the other should only have access to edit subject-tags and team-tags. But as far as the admin site is concerned - the dynamic models do not exist in the permission list, and I can't give anyone permissions regarding them.
i.e. a user given all permissions on the list will still not have access to edit any of the dynamic models, and the only way to let anyone access them at all is to give him a superuser which obviously defies the point
I searched SO and the web and I can't anyone with a similar problem, and the docs don't say anything about this not in the dynamic models section or the proxy models section. so I'm guessing this is a different kind of problem. Any help would be greatly appreciated
UPDATE
So after some research into it, the answer was simple enough. Since permissions in django are objects that are saved to the database, what I needed to do was simple - add the relevent permissions (and create new ContentType objects as well) to the db, and then I could give people specific pemissions.
However, this raised a new question - is it a good convention to put the function that creates the permissions inside create_modeladmin as a find_or_create sort of function (that basically runs every time) or should it be used as an external script that I should run once every time I add a new dynamic model (sort of like how syncdb does it)?
And is there a way to also create the permissions dynamically (which seems to me like the ideal and most fitting solution)?
of course you can create permissions, django have django.contrib.auth.management.create_permissions to do this

Django making sure user and user profile have same pk

Right now I'm using Django's built in admin system to manage users, to which I've attached a profile to contain additional data using the following:
class Profile(models.Model):
user = models.OneToOneField(User, editable = False)
# Data fields here...
As it stands the User and Profile pk (and accordingly id number) will be the same if and only if the profile is created right after the user is created. I could guarantee that this would be the case during the registration process, and while that would cover most uses, creating users with the admin interface could cause mismatched ids to occur. Thus this does not seem like a very robust way to solve this problem and I'd like to hardcode the pk's to be the same. I'm not sure how to do this.
I thought the following would work:
profile_id = models.IntegerField(default=user.pk, editable = False,
primary_key = True)
But it gives me the error:
AttributeError: 'OneToOneField' has no attribute 'pk'
What's the best way to guarantee that the profile and user have the same pk? Note: I'd really rather not deal with extending the base user model as using the OneToOneField to link the two seems to be sufficient for all my needs.
Thanks!
[edit]
My reasoning for asking the question:
My immediate problem was that I wanted a dictionary of values of the User's Profile, which I was retrieving usingprofile_values = Profile.objects.filter(pk=user.id).values()[0]. This highlighted the bug, and I "hacked" around it last night using pk=user.profile.id instead. In the light of the morning this does not seem like such a terrible hack. However, it seems like having pk discrepancies could lead to quiet and hard to catch bugs down the line, and thus forcing them to match up would be a Good Idea. But I'm new to Django so I'd entirely accept that it is, in fact, never a problem if you're writing your code correctly. That said, for almost academic reasons, I'd be curious to see how this might be solved.
[/edit]
Like you already agree that it was never a problem because we have a OneToOne mapping between the two models.
So when you need to get the profile obj corresponding to a User:
profile_values = Profile.objects.get(user_id=user)
assuming,
class Profile(models.Model):
user = models.OneToOneField(User)
...
If your column name is not user, then use the corresponding name in get query.
Still if you are curious as to how to achieve same pk for both models, then we can set a signal on every save of User model. See the documentation.
def create_profile(sender, **kwargs):
if kwargs["created"]:
p = Profile(user=kwargs["instance"], ...)
p.save()
django.db.models.signals.post_save.connect(create_profile, sender=User)
create_profile() will be called every time any User object is saved.
In this function, we create Profile object only if a new User instance has been created.
If we start from blank slate, then I think this will always make sure that a Profile exists for every User and is created right after User was created; which in turn will give same pk for both models.
pk is a parameter in a filter() query, but not a field name. You probably want to use user.id.

Django Admin Inline and Schema Design

My desire is to have a common location model, and then have the various higher level models who need a location refer to it.
I want to present my user in admin with a multiple part form (an inline) that allows them to enter the higher level info for the Publisher and Building, as well as the location information for each. The inline system doesn't seem to want to work this way.
Clearly, I am doing something very wrong, because this seems like a very standard sort of problem to me. Is my schema design borked ?
Am I stupidly using the inline system ? I don't want to do subclasses of Location for each upper level object, because I want to manipulate locations in different ways independent of whatever high-level objects own them (a mailing list, or geographic look up perhaps)
models.py:
...
class Location(models.Model):
"""
A geographical address
"""
# Standard Location stuff
address_line1 = models.CharField("Address line 1", max_length = 45, null=True, blank=True)
...
class Publisher(models.Model):
"""
Contains Publisher information for publishers of yearbooks. Replaces Institution from 1.x
"""
name = models.CharField(max_length=100, null=False, help_text="Name of publisher, e.g. University of Kansas")
groups = models.ManyToManyField(Group, help_text="Select groups that this publisher owns. Usually just one, but multiple groups are possible.")
is_active = models.BooleanField(help_text="Check this box to enable this publisher.")
location = models.OneToOneField(Location)
...
class Building(models.Model):
"""
Contains Building Information
"""
name = models.CharField(max_length=100, null=False, help_text="Name of building, e.g. Physical Sciences")
is_active = models.BooleanField(help_text="Check this box to enable this building.")
location = models.OneToOneField(Location)
...
admin.py:
...
class LocationInline(generic.GenericStackedInline):
model = Location
max_num = 1
extra = 1
class PublisherAdmin(admin.ModelAdmin):
model = Publisher
inlines = [ LocationInline,
]
class BuildingAdmin(admin.ModelAdmin):
model = Building
inlines = [ LocationInline,
]
admin.site.register(Publisher, PublisherAdmin)
admin.site.register(Building, BuildingAdmin)
I can force the inline to load and present by adding this to the Location model:
# Support reverse lookup for admin
object_id = models.PositiveIntegerField()
content_type = models.ForeignKey(ContentType)
of = generic.GenericForeignKey('content_type', 'object_id' )
But when I do this, even though I do get an inline object, and can edit it, the relationship seems backwards to me, with Location storing an id to the object that created it.
Any help is welcome, either a recommended schema change to make everything work wonderfully (as Django is so good at) or a trick to make the backwards-seeming stuff make sense.
Firstly, I think you want ForeignKey, not OneToOneField. Otherwise, you might as well just add your location fields to the Publisher and Building models. Then you'll simply get a dropdown to choose the location and a link to add a new one if needed in the building and publisher admin.
If you really want to have one location instance per building/publisher, you won't be able to edit it as an inline because an inline model needs to have a ForeignKey pointing to the parent model, unless you add the generic foreign key. This isnt 'backwards' - it's a valid option when you want an object to be able to attach itself to any other, regardless of type.
When it comes to domain model, there's no such thing as a "One Right Way" to do it, it depends on your specific application's requirements.
wrt/ your problem:
The OneToOne field limits your models to one Location per model instance, which (as Greg mentionned) is not conceptually very different from just sticking the Location's fields directly in the model. wrt/ DRY/factorisation/reuse etc, you can get this done using model inheritence too, having an abstract (or eventually concrete if it makes sense for your app) Location model.
The ForeignKey solution still restricts your Publisher and Building models to a single Location (which might - or not - be what you want), but a given location might be shared between different Publisher and / or Building instances. This means that editing one given location will reflect on all the related instances (beware of unwanted side effects here).
Using a GenericForeignKey in the Location model means that a given location instance belongs to one and only one related object. No surprinsing side-effect as with the above solution but you may have duplicate locations (ie one for the building, one for the publisher) with same values, and you won't be able to lookup all related objects for a specific location (or not that easily at least). Also, this won't prevent a Publisher or Building instance to have more than one location, which once again might be fine or not. wrt/ Location instance "storing the id" of the object they belong to, well, that's really what this design choice means : a Location "belongs to" some other object, period.
In any case, designing around the default behaviour of Django's admin app is probably not the wisest thing to do. You have to first decide what makes sense for this application (and you may have different needs for Publishers and Buildings), then possibly extend the admin to match your needs.

Django - Customizeable UserProfile

So I've got a UserProfile in Django that has certain fields that are required by the entire project - birthday, residence, etc. - and it also contains a lot of information that doesn't actually have any importance as far as logic goes - hometown, about me, etc. I'm trying to make my project a bit more flexible and applicable to more situations than my own, and I'd like to make it so that administrators of a project instance can add any fields they like to a UserProfile without having to directly modify the model. That is, I'd like an administrator of a new instance to be able to create new attributes of a user on the fly based on their specific needs. Due to the nature of the ORM, is this possible?
Well a simple solution is to create a new model called UserAttribute that has a key and a value, and link it to the UserProfile. Then you can use it as an inline in the django-admin. This would allow you to add as many new attributes to a UserProfile as you like, all through the admin:
models.py
class UserAttribute(models.Model):
key = models.CharField(max_length=100, help_text="i.e. Age, Name etc")
value = models.TextField(max_length=1000)
profile = models.ForeignKey(UserProfile)
admin.py
class UserAttributeInline(admin.StackedInline):
model = UserAttribute
class UserProfile(admin.ModelAdmin):
inlines = [UserAttibuteInline,]
This would allow an administrator to add a long list of attributes. The limitations are that you cant's do any validation on the input(outside of making sure that it's valid text), you are also limited to attributes that can be described in plain english (i.e. you won't be able to perform much login on them) and you won't really be able to compare attributes between UserProfiles (without a lot of Database hits anyway)
You can store additional data in serialized state. This can save you some DB hits and simplify your database structure a bit. May be the best option if you plan to use the data just for display purposes.
Example implementation (not tested)::
import yaml
from django.db import models
class UserProfile(models.Model):
user = models.OneToOneField('auth.User', related_name='profile')
_additional_info = models.TextField(default="", blank=True)
#property
def additional_info(self):
return yaml.load(self._additional_info)
#additional_info.setter
def additional_info(self, user_info_dict):
self._additional_info = yaml.dump(user_info_dict)
When you assign to profile.additional_info, say, a dictionary, it gets serialized and stored in _additional_info instead (don't forget to save the instance later). And then, when you access additional_info, you get that python dictionary.
I guess, you can also write a custom field to deal with this.
UPDATE (based on your comment):
So it appears that the actual problem here is how to automatically create and validate forms for user profiles. (It remains regardless on whether you go with serialized options or complex data structure.)
And since you can create dynamic forms without much trouble[1], then the main question is how to validate them.
Thinking about it... Administrator will have to specify validators (or field type) for each custom field anyway, right? So you'll need some kind of a configuration option—say,
CUSTOM_PROFILE_FIELDS = (
{
'name': 'user_ip',
'validators': ['django.core.validators.validate_ipv4_address'],
},
)
And then, when you're initializing the form, you define fields with their validators according to this setting.
[1] See also this post by Jacob Kaplan-Moss on dynamic form generation. It doesn't deal with validation, though.