Can not add a group to a auth user - python

I want create a user with custom group in django admin.
so I write below code:
from django.contrib.auth.models import User as AuthUser
from django.contrib.auth.models import Group
# these groups have already been created.
class TestGroup(object):
Admin = 'Admin'
Merchant = 'Merchant'
User = 'User'
class Merchant(AuthUser):
def save(self, **kwargs):
super(Merchant, self).save(**kwargs)
for group in Group.objects.all():
print group.name
# way 1
if not self.groups.filter(name=TestGroup.Merchant).exists():
print self.groups.filter(name='Merchant').exists()
g = Group.objects.get(name=TestGroup.Merchant)
g.user_set.add(self)
print self.groups.filter(name='Merchant').exists()
# way 2
if not self.groups.filter(name=TestGroup.Merchant).exists():
g = Group.objects.get(name=TestGroup.Merchant)
self.groups.add(g)
# way 3
if not self.groups.filter(name=TestGroup.Merchant).exists():
g = Group.objects.get(name=TestGroup.Merchant)
self.groups.add(g)
self.save()
I have tried three ways to add a group to a user.But none of them could work.
UPDATE:
You can test by following this steps:
create a group named 'Merchant' at django admin
add my code (add print in way 1 to test), syncdb and so on.
create a Merchant at django admin.
you can see log:
u'Merchant'
False
True
enter the merchant you just created, you can see, the group merchant is not selected(means this user do not beyond to this group).
click save again,
you would still see
u'Merchant'
False
True
add group to merchant fail, very strange.

Thanks for your clarifications. From what I can see your problem is the way you're trying to filter and get the Group. You're checking whether the name equals an object instance: TestGroup.Merchant when it expects a string. Try:
if not self.groups.filter(name='Merchant'):
g = Group.objects.get(name='Merchant')
g.user_set.add(self)
I've also removed the exists part of your if statement because an empty queryset will evaluate to Boolean False anyway so exists is redundant.
EDIT: Following your comment I'm back to the comment I put on your question. You are assigning strings to variables, not creating groups. If the groups are already created the TestGroup class is completely redundant and should be removed.
At your command prompt import django's Group class and try various things until you can select your groups. Change your code to reflect whatever worked at the command line. For example:
for group in Group.objects.all():
print group.name
Then change your code to look up Group.objects.get(name='relevant_name').

I debug into save method and find:
ValueError: "<Merchant: m23533>" needs to have a value for field "user" before this many-to-many relationship can be used.
It is saying this user have not saved to db yet, that's why adding groups fails!
solution:
class Merchant(AuthUser):
def save(self, **kwargs):
super(Merchant, self).save(force_insert=True)
if not self.groups.filter(name=TestGroup.Merchant).exists():
g = Group.objects.get(name=TestGroup.Merchant)
g.user_set.add(self)

Related

Django fake model instanciation - No testunit [duplicate]

This question already has an answer here:
How can I create a django model instance with deferred fields without hitting the database?
(1 answer)
Closed 8 months ago.
I want to know if I can instanciate an empty fake model just with id of database record.
I found way to create mockup model, but I want a production-friendly solution.
Explanation of my issue :
I want to list users settings for users who choose to be displayed on public mode :
user_displayed_list = UserPublicProfile.objects.filter(
displayed = True,
).only(
'user_id',
'is_premium',
)
user_settings_list = []
for user_displayed in user_displayed_list:
# I have to send user Instance to the next method :
user_settings = self.get_user_settings(user_displayed.user)
user_settings_list.append(user_settings)
# But ’user_displayed.user’ run an new SQL query
I know I can improve my queryset as :
user_displayed_list = UserPublicProfile.objects.filter(
displayed = True,
).select_related(
'user'
).only(
'user',
'is_premium',
)
But It makes an useless join because I need only the user id field in get_user_settings():
The get_user_settings() method (it could help to understand context):
def get_user_settings(self, user)
user_settings = UserSettings.objects.get(user = user)
return user_settings
In real project, this method run more business feature
Is there a way to instanciate a User model instance with only id field filled ?
I don't want to use a custom empty class coded for this purpose. I really want an object User.
I didn't find anything for that. If it's possible, I could use it by this way :
for user_displayed in user_displayed_list:
FakeUser = User.objects.create_fake(id = user_displayed.user_id)
# I have to send user Instance to the next method :
user_settings = self.get_user_settings(FakeUser)
Without seeing the complete models, I'm assuming a bit. Assuming that UserSettings has a ForeignKey to User. Same for UserPublicProfile. Or User has ForeignKey to UserSettings. Works as well.
Assuming that, I see two solutions.
Solution #1; use the ORM to full potential
Just saw your comment about the 'legacy method, used many times'.
Django relations are very smart. They accept either the object or the ID of a ForeignKey.
You'd imagine this only works with a User. But if you pass the id, Django ORM will help you out.
def get_user_settings(self, user)
user_settings = UserSettings.objects.get(user = user)
return user_settings
So in reality, these work the same:
UserSettings.objects.get(user=1)
UserSettings.objects.get(user_id=1)
Which means this should work, without a extra query:
user_displayed_list = UserPublicProfile.objects.filter(
displayed = True,
).only(
'user_id',
'is_premium',
)
user_settings_list = []
for user_displayed in user_displayed_list:
# I have to send user Instance to the next method :
user_settings = self.get_user_settings(user_displayed.user_id) # pass the user_id instead of the object.
user_settings_list.append(user_settings)
Solution #2: chain relations
Another solution, again, still assuming quite a bit ;)
It would think you can chain the model together.
Assuming these FK exists: UserPublicProfile -> User -> UserSetting.
You could do this:
user_displayed_list = UserPublicProfile.objects.filter(
displayed = True,
).select_related(
'user', 'user__usersettings', # depends on naming of relations
).only(
'user',
'is_premium',
)
for user_displayed in user_displayed_list:
# I have to send user Instance to the next method :
user_settings = user_displayed.user.usersettings # joined, so should cause no extra queries. Depends on naming of relations.
user_settings_list.append(user_settings)

How can I make a field in Django models that concatenates a specific string to each record's id?

I have made a field facility_id in Django models that should concatenate a specific string "ACCTS-" on the left with each record's id on the right,
My model class is below:
class Facility(models.Model):
...
id = models.BigAutoField(primary_key=True)
facility_id = models.CharField(max_length=50, default=print(f'{"ACCTS-"}{id}'), editable=False)
...
I want to the facility_id field to be storing special and readable human friendly facility_id's of the form: ACCTS-1, ACCTS-2, ACCTS-3, ... corresponding to each individual id.
The migrations didn't throw any errors, however When I try to create the records for this table in the Django Admin, am getting an IntegrityError of:
IntegrityError at /admin/ACCTS_CLYCAS/facility/add/
NOT NULL constraint failed: ACCTS_CLYCAS_facility.facility_id
How do I fix this problem, or what could be the easiest way to implement my problem.
The migrations didn't throw any errors, however When I try to create the records for this table in the Django Admin
That makes sense, since you have set the default=None. Indeed, print(…) returns None and only prints the value to the standard output channel (stdout). It will thus not prepend the value of the id with ACCTS.
If the facility_ids are all just the id prefixed with ACCTS-, you can work with a #property instead:
class Facility(models.Model):
id = models.BigAutoField(primary_key=True)
#property
def facility_id(self):
return f'ACCTS-{self.id}'
You can also try using a post save signal.
Add blank = True to facility_id and then use a post save signal to update the value of facility_id.
You can watch this tutorial on how to use Django Signals

How to control field name for related model fetching in Peewee?

I have two models: User and Message. Each Message has two references to User (as sender and as receiver). Also I have defined an other_user hybrid method for Message which returns "user other than specific one" - see below:
from peewee import *
from playhouse.hybrid import hybrid_method
from playhouse.shortcuts import case
class User(Model):
name = CharField()
class Message(Model):
sender = ForeignKeyField(User, related_name='messages_sent')
receiver = ForeignKeyField(User, related_name='messages_received')
text = TextField()
#hybrid_method
def other_user(self, user):
if user == self.sender:
return self.receiver
elif user == self.receiver:
return self.sender
else:
raise ValueError
#other_user.expression
def other_user(cls, user):
return case(user.id, (
(cls.sender, cls.receiver),
(cls.receiver, cls.sender)))
Now I want to make a composite query which will retrieve all messages for current user and also retrieve information about "other" user than current. Here is how I do it:
current_user = request.user # don't matter how I retrieve it
query = (Message.select(Message, User)
.where(
(Message.sender == current_user) |
(Message.receiver == current_user))
.join(User, on=(User.id == Message.other_user(current_user))))
This query works well - i.e. it retrieves the exact information I need.
But here is the problem: "other user" information is always saved as sender field.
If I use this with models which have no direct ForeignKey reference then peewee creates a new field (in this case it would be named user) for additional requested model. But if there is at least one ForeignKey relationship from primary model to secondary requested model then it uses first such relationship.
Is it possible to somehow override this behaviour?
I tried Model.alias() method, but it (unlike Node.alias) doesn't allow to specify name.
I'm not completely sure what you want, so I'll provide a snippet that will likely work for your specific scenario, and will hopefully also allow you to learn how to do what you want, if this isn't it:
SenderUser = User.alias()
ReceiverUser = User.alias()
OtherUser = User.alias()
query = (Message.select(Message, SenderUser, ReceiverUser)
.where(
(Message.sender == current_user) |
(Message.receiver == current_user))
.join(SenderUser, on = (Message.sender == SenderUser.id).alias('sender'))
.switch(Message)
.join(ReceiverUser, on = (Message.receiver == ReceiverUser.id).alias('receiver'))
.switch(Message)
.join(OtherUser, on = (Message.other_user(current_user) == OtherUser.id).alias('other_user'))
Notes:
You don't really need to create all those aliases (SenderUser/ReceiverUser/OtherUser), just two, and use User for the other. I just find that the query becomes more readable like this.
When you define an alias in the on clause, you basically tell peewee in which variable to store the joined table. I'm sending them directly to the already existing properties (sender/receiver). Also, I'm creating an extra property in the model with the value of the other user, which you can access as usual with self.other_user.
That switch method switches the current context to Message, so you can join a new table to Message instead of the SenderUser/ReceiverUser contexts where you end up after the two first joins.
If for some reason you're joining something that might be undefined (which doesn't seem to be the case here as both users are likely mandatory), you would probably want to add that you want a left outer join, like this:
.join(ReceiverUser, JOIN.LEFT_OUTER, on = (Message.receiver == ReceiverUser.id).alias('receiver'))
Don't forget to from peewee import JOIN
Something else I just noticed, is that you likely want to change that other_user method you have to compare ids instead of the model variables. If self.sender is not filled when you access it, peewee will trigger a database select to get it, so your other_user method possibly triggers 2 select queries. I would do it like:
#hybrid_method
def other_user_id(self, user):
if user.id == self.sender_id:
return self.receiver_id
elif user.id == self.receiver_id:
return self.sender_id
else:
raise ValueError
You can see that I use sender_id instead of sender.id. That uses the ids for each foreign key that are already set in the message model. If you did self.receiver.id you would likely trigger that select anyway, to then access the id property (I'm not 100% sure here though).

Django admin runs query multiple times

I have a function defined in list_items in django admin as follows:
def won(self):
result = (Result.objects.filter(user=self)
.select_related('user')
.aggregate(Sum('prize'))['prize__sum'])
return result if result else 0
A result contains a foreign key to user, as well as a 'prize', defining the amount of points a user earned.
Using Django Debug Toolbar, I've discovered that each user being listed results in 40 identical queries being run to calculate the 'won' value for each user.
SELECT SUM("games_result"."prize") AS "prize__sum" FROM "games_result" WHERE "games_result"."user_id" = 28297
Duplicated 40 times.
Am I doing something really obviously wrong to cause this problem? Is there a better way of getting the Result objects with a link to this user?
Considering your user has a field named results, which represents all of the results from the games for a certain user you can use annotate aggregation function to add a field to each user and then on every user you can simpy select the prize_sum as in this example:
class UserAdmin(admin.ModelAdmin):
list_display = ('won', )
def won(self):
return self.prize_sum
def get_queryset(self, request):
return super(UserAdmin, self).get_queryset(
request).select_related('results').annotate(prize_sum=Sum('results__prize'))

How to select and limit the related_name connection in the Peewee ORM?

I'm using Flask with the Peewee ORM in which I have defined two tables like so:
class Ticket(db.Model):
created = DateTimeField(default=datetime.now)
customer_uuid = CharField() # the customer's UUID gotten from App. More info comes from bunq API.
ticket_type = ForeignKeyField(TicketType, related_name='tickets')
active = BooleanField(default=True)
class Assign(db.Model):
created = DateTimeField(default=datetime.now)
ticket = ForeignKeyField(Ticket, related_name='assigned_to')
user = ForeignKeyField(User, related_name='assigned_tickets')
In the Assign table, several users can be assigned to a ticket, but only the last one counts (i.e., if a new user gets assigned, the previous ones should be disregarded). So I select the active tickets using the following:
open_tickets = Ticket.select().where(Ticket.active == True)
I now want to use this loop in my template. With every iteration however, I also want to display the assigned user. But open_ticket[0].assigned_to obviously returns several assignments, and with it several users.
Would anybody know how I can get the latest assigned user for every ticket within a loop?
This worked for me in Sqlite:
q = (Ticket
.select(Ticket, Assign, User)
.join(Assign)
.join(User)
.group_by(Ticket)
.order_by(Ticket.id, Assign.id.desc()))
for ticket in q:
print ticket.id, ticket.assign.user.username

Categories