django - limit the number of users - django

(sorry for my english)
Only a question, exist any way to limit the number of users that can be created in a Django App?
I search in a lot of places and i only find this, but i see in the repo that the last update was 3 years ago https://github.com/1stvamp/django-limit-users
I don't know if exist any way in the core of django or if i have to override something!
Thanks very much!

While I don't have time to test https://github.com/1stvamp/django-limit-users against a new Django, it goes in the right direction using django's signals: https://docs.djangoproject.com/en/dev/ref/signals/
So for example, you could write a pre_save or a post_save handler and connect it to the signals emitted before / after saving your user model.
A simple post_save handler could look like:
def user_post_save(sender, instance, created, **kwargs):
if created and sender.objects.count() > MY_LIMIT:
instance.is_active = False
instance.save()
A simple pre_save handler would look like:
def user_pre_save(sender, instance, **kwargs):
if instance.id is None and sender.objects.count() > MY_LIMIT:
instance.is_active = False # Make sure the user isn't active
Instead of the last line in the pre_save handler, you could also raise an Exception to make sure the User isn't even saved to the DB.
Another option would be to combine this with a custom user model so instead of is_active you could use over_limit or whatever you want. The repo you linked is achieving that with a separate DisabledUser model.

Related

M2M relationship validation in Django

I have these two models:
class Test(models.Model):
problems = models.ManyToManyField('Problem')
...
class Problem(models.Model):
type = models.CharField(max_length=3, choices=SOME_CHOICES)
...
Now, while adding Problems to a Test, I need to limit the number of particular type of problems in the Test. E.g. a Test can contain only 3 Problems of type A, and so on.
The only way to validate this seems to be by using m2m_changed signal on Test.problems.through table. However, to do the validation, I need to access the current Problem being added AND the existing Problems - which doesn't seem to be possible somehow.
What is the correct way to do something like this? M2M validation seems to be a topic untouched in the docs. What am I missing?
You are right on the part that you have to register an m2m_changed signal function like the following:
def my_callback(sender, instance, action, reverse, model, pk_set, **kwargs)
If you read the documentation you 'll see that sender is the object-model that triggers the change and model is the object-model that will change. pk_set will give you the pkeys that will be the new reference for your model. So in your Test model you have to do something like this:
#receiver(m2m_changed)
def my_callback(sender, instance, action, reverse, model, pk_set, **kwargs):
if action == "pre_add":
problem_types = [x.type for x in model.objects.filter(id__in=pk_set)]
if problem_types.count("A") > some_number:
raise SomeException
Mind though that an Exception at that level will not be caught if you're entering fields from Django admin site. To be able to provide user friendly errors for django admin data entry, you'll have to register your own form as admin form. In your case, you need to do the following:
class ProblemTypeValidatorForm(ModelForm):
def clean(self):
super(ProblemTypeValidatorForm, self).clean()
problem_types = [x.type for x in self.cleaned_data.get("problems") if x]
if problem_types.count("A") > some_number:
raise ValidationError("Cannot have more than {0} problems of type {1}"
.format(len(problem_types), "A")
then in your admin.py
#admin.register(Test)
class TestAdmin(admin.ModelAdmin):
form = ProblemTypeValidatorForm
Now keep in mind that these are two different level implementations. None will protect you from someone doing manually this:
one_test_object.problems.add(*Problem.objects.all())
one_test_object.save()
Personal opinion:
So keeping in mind the above, I suggest you go with the ModelForm & ModelAdmin approach and if you're providing an API for CRUD operations, make your validations there as well. Nothing can protect you from someone entering stuff in your db through django shell. If you want such solution types you should go directly to your db and write some kind of magic trigger script. But keep in mind that your db is actually data. Your backend is the one with the business logic. So you shouldn't really try to impose business rules down to the db level. Keep the rules in your backend by validating your data at the spots where create/update happens.
You can't override save for a M2M I'm afraid, but you can achieve what you want.
Use the m2m_changed signal where the action is pre_add.
The 'instance' kwarg will be the Test model the problem is being added to.
The 'pk_id' kwarg will be the primary key of the Problems being added (1 or more).
The validation logic will be something like this:
p_type = Problem.objects.get(id=kwargs['pk_id']).type
type_count = kwargs['instance'].problems.filter(type=p_type).count()
if p_type == 'A' and type_count == 3:
raise Exception("cannot have more than 3 Problems of type A")
[sorry don't have django on hand to verify the query]

django "exclusive" checkbox

I was wondering about how could I have in Django an "exclusive" Checkbox and set it in the admin? What I mean is that I have an app that stores polls and there can only be one in the frontpage, so I add a front_page field to the DB and make it bool. True show, False don't. So whenever I set in the admin a poll's front_page field to true I want every other poll to be set to false, this editing being done in the admin. As a Flasker I can think of 100 ways of doing it elsewhere, but this django thing has got me confused. Thanks!
Basically what you want to do is override the save method of the model that has the exclusive flag, and then in the save method check to see if the flag is turned on. If it is, then do a query to find all the records that have the flag turned on and turn them off. Then save. It would look something like this:
class Poll(models.Model):
#...
front_page = models.BooleanField()
#...
def save(self, *args, **kwargs):
if self.front_page:
Poll.objects.filter(front_page=True).update(front_page=False)
super(Poll, self).save(*args, **kwargs)
Hope this helps!

Django - How to save m2m data via post_save signal?

(Django 1.1) I have a Project model that keeps track of its members using a m2m field. It looks like this:
class Project(models.Model):
members = models.ManyToManyField(User)
sales_rep = models.ForeignKey(User)
sales_mgr = models.ForeignKey(User)
project_mgr = models.ForeignKey(User)
... (more FK user fields) ...
When the project is created, the selected sales_rep, sales_mgr, project_mgr, etc Users are added to members to make it easier to keep track of project permissions. This approach has worked very well so far.
The issue I am dealing with now is how to update the project's membership when one of the User FK fields is updated via the admin. I've tried various solutions to this problem, but the cleanest approach seemed to be a post_save signal like the following:
def update_members(instance, created, **kwargs):
"""
Signal to update project members
"""
if not created: #Created projects are handled differently
instance.members.clear()
members_list = []
if instance.sales_rep:
members_list.append(instance.sales_rep)
if instance.sales_mgr:
members_list.append(instance.sales_mgr)
if instance.project_mgr:
members_list.append(instance.project_mgr)
for m in members_list:
instance.members.add(m)
signals.post_save.connect(update_members, sender=Project)
However, the Project still has the same members even if I change one of the fields via the admin! I have had success updating members m2m fields using my own views in other projects, but I never had to make it play nice with the admin as well.
Is there another approach I should take other than a post_save signal to update membership? Thanks in advance for your help!
UPDATE:
Just to clarify, the post_save signal works correctly when I save my own form in the front end (old members are removed, and new ones added). However, the post_save signal does NOT work correctly when I save the project via the admin (members stay the same).
I think Peter Rowell's diagnosis is correct in this situation. If I remove the "members" field from the admin form the post_save signal works correctly. When the field is included, it saves the old members based on the values present in the form at the time of the save. No matter what changes I make to the members m2m field when project is saved (whether it be a signal or custom save method), it will always be overwritten by the members that were present in the form prior to the save. Thanks for pointing that out!
Having had the same problem, my solution is to use the m2m_changed signal. You can use it in two places, as in the following example.
The admin upon saving will proceed to:
save the model fields
emit the post_save signal
for each m2m:
emit pre_clear
clear the relation
emit post_clear
emit pre_add
populate again
emit post_add
Here you have a simple example that changes the content of the saved data before actually saving it.
class MyModel(models.Model):
m2mfield = ManyToManyField(OtherModel)
#staticmethod
def met(sender, instance, action, reverse, model, pk_set, **kwargs):
if action == 'pre_add':
# here you can modify things, for instance
pk_set.intersection_update([1,2,3])
# only save relations to objects 1, 2 and 3, ignoring the others
elif action == 'post_add':
print pk_set
# should contain at most 1, 2 and 3
m2m_changed.connect(receiver=MyModel.met, sender=MyModel.m2mfield.through)
You can also listen to pre_remove, post_remove, pre_clear and post_clear. In my case I am using them to filter one list ('active things') within the contents of another ('enabled things') independent of the order in which lists are saved:
def clean_services(sender, instance, action, reverse, model, pk_set, **kwargs):
""" Ensures that the active services are a subset of the enabled ones.
"""
if action == 'pre_add' and sender == Account.active_services.through:
# remove from the selection the disabled ones
pk_set.intersection_update(instance.enabled_services.values_list('id', flat=True))
elif action == 'pre_clear' and sender == Account.enabled_services.through:
# clear everything
instance._cache_active_services = list(instance.active_services.values_list('id', flat=True))
instance.active_services.clear()
elif action == 'post_add' and sender == Account.enabled_services.through:
_cache_active_services = getattr(instance, '_cache_active_services', None)
if _cache_active_services:
instance.active_services.add(*list(instance.enabled_services.filter(id__in=_cache_active_services)))
delattr(instance, '_cache_active_services')
elif action == 'pre_remove' and sender == Account.enabled_services.through:
# de-default any service we are disabling
instance.active_services.remove(*list(instance.active_services.filter(id__in=pk_set)))
If the "enabled" ones are updated (cleared/removed + added back, like in admin) then the "active" ones are cached and cleared in the first pass ('pre_clear') and then added back from the cache after the second pass ('post_add').
The trick was to update one list on the m2m_changed signals of the other.
I can't see anything wrong with your code, but I'm confused as to why you think the admin should work any different from any other app.
However, I must say I think your model structure is wrong. I think you need to get rid of all those ForeignKey fields, and just have a ManyToMany - but use a through table to keep track of the roles.
class Project(models.Model):
members = models.ManyToManyField(User, through='ProjectRole')
class ProjectRole(models.Model):
ROLES = (
('SR', 'Sales Rep'),
('SM', 'Sales Manager'),
('PM', 'Project Manager'),
)
project = models.ForeignKey(Project)
user = models.ForeignKey(User)
role = models.CharField(max_length=2, choices=ROLES)
I've stuck on situation, when I needed to find latest item from set of items, that connected to model via m2m_field.
Following Saverio's answer, following code solved my issue:
def update_item(sender, instance, action, **kwargs):
if action == 'post_add':
instance.related_field = instance.m2m_field.all().order_by('-datetime')[0]
instance.save()
m2m_changed.connect(update_item, sender=MyCoolModel.m2m_field.through)

Post create instance code call in django models

Sorry for some crazy subj.
I'd like to override django models save method and call some additional code if the model instance is newly created.
Sure I can use signals or check if the model have empty pk field and if yes, create temporary variable and later call a code:
Class EmailModel(models.Model):
email = models.EmailField()
def save(self, *args, **kwargs)
is_new = self.pk is None
super(EmailModel, self).save(*args, **kwargs)
# Create necessary objects
if is_new:
self.post_create()
def post_create(self):
# do job, send mails
pass
But I like to have some beautiful code and avoid using temporary variable in save method.
So the question is: is it possible to find if the instance of model is newly created object just after super save_base parent method call?
I've checked django sources can't find how to do that in right way.
Thanks
We have related post
For real - signals are best approch in this case.
You could use post_save() signal and in the listener just check if the credit_set exist for current model instance and if not - create one. That would be my choice - there is no need to overdo such a simple task.
Of course if you really need to know exactly when the model was initiated (I doubt it) use post_init() signal. You don't need to override save() method just to set some additional variables. Just catch post_init() signal, or pre_save(), and just change/add what you want. IMHO there is no sense to override save() method and check if this is new instance or not - that's why the signals are there.

Django pre_save signal called when commit=False

I am writing a Django application that will track changes to the models, in a similar way to the admin interface. For example, I will be able to display a list of changes to a model, that look something like Changed Status from Open to Closed.
I am using the pre_save signal to do this, comparing the relevant fields between the existing item in the database, and the "instance" which is being saved. To get the existing item, I have to do sender._default_manager.get(pk=sender.pk) which seems a bit messy, but that part works.
The problem is, the view for changing this model calls the save() method on the form twice (first with commit=False) - this means that 2 changes get logged in the database, as the pre_save signal is emitted twice.
Is there any way I can accomplish this? Maybe in a different way altogether, though I remember reading that the Django admin app uses signals to track changes that users make.
Looking through the Django source, it seems that pre_save signals are sent on every call to save, even if commit is false. I would suggest inserting on the first pre_save, but add a flag column to the changes table, e.g.
class FooChanges(models.Model):
foo = models.ForeignKey(Foo)
dt = models.DateTimeField(default=datetime.now)
field = models.CharField(max_length=50)
value = models.CharField(max_length=50) # Or whatever is appropriate here
finished = models.BooleanField(default=False)
Then, your presave can be:
def pre_save_handler(sender, instance):
foo_changes, created = FooChanges.objects.get_or_create(foo=instance, finished=False, field='Status', value=instance.status)
if not created:
foo_changes.finished = True
foo_changes.save()
So on the first pre_save, you actually insert the change. On the second pass, you retrieve it from the database, and set the flag to false to make sure you don't pick it up the next time Foo's status changes.
use dispatch_uid:
http://docs.djangoproject.com/en/1.2/topics/signals/#preventing-duplicate-signals
Django Audit Log
django-audit-log is a pluggable app that does exactly what you want with little effort. I've used it in a project and I'll surely use it in many more now that I know it.