I have this simple friendship model for users. I am trying to get the friends of the user that is currently logged in. Since friends means a relationship doesn't matter if you are user or friend I would like to get all the users that are not the current user.
The friendship model:
# WHEN A USER BECOMES FRIENDS WITH ANOTHER USER
class Friendship(models.Model):
user = models.ForeignKey(
User, on_delete=models.CASCADE
)
friend = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="friends"
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name="created at")
updated_at = models.DateTimeField(auto_now=True, verbose_name="updated at")
class Meta:
unique_together = ["user", "friend"]
verbose_name = "friendship"
verbose_name_plural = "friendships"
ordering = ["created_at"]
def __str__(self):
return "{} is friends with {}".format(self.user, self.friend)
def save(self, *args, **kwargs):
if self.user == self.friend:
return "Same person friendship should happen mentally"
else:
super().save(*args, **kwargs)
The function I am using to get the friends:
def get_friends(queryset, request, *args, **kwargs):
id = kwargs['user']
user = User.objects.get(id=id)
friends = [f.user for f in Friendship.objects.all().filter((Q(user=user) | Q(friend=user)))]
return queryset.filter(user__in=friends)
I keep getting the logged in user as friend instead of the other users that are not the logged in user. What am I doing wrong here please?
If two people are friends then they are friends both ways. So with the following data:
users = {
id: 1,
id: 2,
id: 3,
id: 4,
id: 5
}
friendship = {
(user: 1, friend: 3),
(user: 2, friend: 1),
(user: 3, friend: 4),
(user: 3, friend: 5)
}
user 3's friends are user 1, user 4, user 5
So return user 1, user 4 and user 5
Instead I get user 3, which is wrong
This seems really complicated but I simply want to return all friendship objects and if the user field is equal to the current user then return the user listed in the friend field and then obviously the other way around also. If the current user is listed in the friend field return the user in the user field.
You can update the query to exclude the logged-in user so:
def get_friends(queryset, request, *args, **kwargs):
id = kwargs['user']
user = User.objects.get(id=id)
friends = [f.friend for f in user.friends.all()]
return queryset.filter(user__in=friends)
Edit
Try to exclude the current user from the list of friends so:
def get_friends(queryset, request, *args, **kwargs):
id = kwargs['user']
user = User.objects.get(id=id)
friends = Friendship.objects.filter(Q(user=user) | Q(friend=user)).exclude(user=user, friend=user)
friend_users = [friend.user if friend.user != user else friend.friend for friend in friends]
return queryset.filter(user__in=friend_users)
Note: It is better to use get_object_or_404() instead of get() as it calls get() on a given model manager, but it raises Http404 instead of the model's DoesNotExist exception.
Related
I want to create a basic approval system in my Django project. In this system there are several ranks, but for this question I only use Lead and Manager. I created forms and this forms are representing limits.
Only Lead can fill these forms. But what I want is when a Lead update the form it shouldn't display without Manager's approval. How can I do that?
approvals/models.py
class DoaTable(models.Model):
LIMITS = (
('Low Risk', 'Low Risk'),
(...),
('Strict Credit Check', 'Strict Credit Check'),
('No Credit Check', 'No Credit Check'),
)
RANKS = (
('Analyst', 'Analyst'),
('Senior Analyst', 'Senior Analyst'),
('Lead', 'Lead'),
('Manager', 'Manager'),
('...Officer'),
)
rank = models.CharField(max_length=200, choices=RANKS)
risk = models.CharField(max_length=200, choices=LIMITS)
limit = models.FloatField()
comp_name = models.ForeignKey(CompanyProfile, on_delete=models.CASCADE, null=True)
user/models.py
class UserProfile(AbstractUser):
...
password = models.CharField(max_length=250)
email = models.EmailField(max_length=254)
rank = models.CharField(max_length=200)
...
class Rank(models.Model):
rank_name = models.CharField(max_length=200)
company = models.ForeignKey(CompanyProfile, on_delete=models.CASCADE, null=True, unique=False)
Ranks in this model is same as Doa table ranks. We assume that user ranks are Lead and Manager for this scenerio.
approvals/forms.py
class DoaTableForm(forms.ModelForm):
class Meta:
model = DoaTable
fields = ('rank', 'risk', 'limit',)
class UpdateDoaTableForm(forms.ModelForm):
class Meta:
model = DoaTable
fields = ('limit',)
aprovals/views.py
def update_limit(request, id):
limiting = get_object_or_404(DoaTable, id=id)
form = UpdateDoaTableForm(request.POST or None, request.FILES or None, instance=limiting)
limiting_item = DoaTable.objects.filter(id=id)
if form.is_valid():
form.save()
return redirect('approvals:update_limit_list')
context = {
'form': form,
'limiting_item': limiting_item
}
return render(request, 'limitUpdate.html', context)
1. How to do it with your current architecture
Add a new column to your DoaTable model to reflect whether it should be displayed or not and only display it in your view if doatable.should_display is True:
approvals/models.py
class DoaTable(models.Model):
# ....
should_display = models.BooleanField(default=False)
rank = models.CharField(max_length=200, choices=RANKS)
# ...
Then override your ModelForm's __init__() to accept the current user and clean() method to check for the rank:
approvals/forms.py
from django.core.exceptions import ValidationError
# ...
class UpdateDoaTableForm(forms.ModelForm):
class Meta:
model = DoaTable
fields = ('limit',)
def __init__(self, *args, user, **kwargs):
super().__init__(*args, **kwargs)
self.user = user
def clean(self):
cleaned_data = super().clean()
if self.user.rank != "Lead": # BAD: hardcoded value
raise ValidationError(
"You do not have the required rank."
)
return cleaned_data # Always return the cleaned data
Pass in the request.user in your view:
approvals/views.py
def update_limit(request, id):
# ...
form = UpdateDoaTableForm(request.POST or None, request.FILES or None, user=request.user, instance=limiting)
# ...
2. Suggested ways of doing it
AbstractUser comes in with groups and permissions which you can utilize to check if your user belongs to a certain group or has a certain permission before doing an action (in this case updating/approving forms), for example permissions could be: 'fill_form_perm', 'approve_form_perm' and your groups could be: 'lead', 'officer'.
You can make use of IntegerChoices for the ranks in your model then check the level of permission your user has by a doing a simple comparison. This is more flexible as you can chain in multiple ranks, for example below Manager but above Senior Anaylist in one condition without too much of a hassle.
I am a newbie at Django and I have come across this problem with my code.
I have a Custom User Model and an Account model which are related by many-to-many field.
During SignUp a user is asked to either create an Account or not ( join other account through a link ).
If the User creates an Account then he is the owner of the account and other Users can join the account.(Did not finish the code for ownership)
One User can be a part of several accounts at the same time.
Creation of Account(or not) and the User takes place in the Signup view.
I read up about the nested serializer in the documentation and i think this should create the two models instances.
How to create relationships in one view using nested serializers?
Other Approaches to solve the issue?
Models
class Account(models.Model):
AccountName = models.TextField(max_length=100, blank=False, null=False)
class User(AbstractBaseUser):
AccountName = models.ManyToManyField(Account)
CreateAccount = models.BooleanField(blank=False, null=False)
EmailId = models.EmailField(max_length=128, blank=False, null=False, unique=True)
is_admin = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
USERNAME_FIELD = 'EmailId'
REQUIRED_FIELDS = ['AccountName', 'CreateAccount',]
# Implemented the other req. functions
objects = MyAccountManager()
Serializers
class AccountCreationSerializer(ModelSerializer):
class Meta:
model = Account
fields = ['AccountName']
class SignUpSerializer(ModelSerializer):
AccountName = AccountCreationSerializer()
class Meta:
model = User
fields = ['EmailId', 'AccountName', 'CreateAccount', 'password']
extra_kwargs = {'password': {'write_only': True, 'required': True}}
def create(self, validated_data):
AccountName = validated_data.pop('AccountName')
if validated_data['CreateAccount']: #Create only when this is True
Account.objects.create(AccountName=AccountName, **AccountName)
userAcc = User.objects.create_user(**validated_data)
return userAcc
View
class SignUpView(APIView):
def post(request):
# to edit
signup_serializer = SignUpSerializer(data=request.data)
# rest of the view
The request
// Ignoring the quotes
EmailID: xyz#gmail.com
AccountName: TestAcc
CreateAccount: False
Password: ****
Error:
Direct assignment to the forward side of a many-to-many set is prohibited. Use AccountName.set() instead.
Create_user in Custom model
def create_user(self, EmailId, AccountName, CreateAccount, password):
if not EmailId:
raise ValueError("Users must have an email")
user = self.model(
EmailId=self.normalize_email(EmailId),
AccountName=AccountName,
CreateAccount=CreateAccount,
)
user.set_password(password)
user.save(using=self._db)
return user
I am pretty sure I am making some mistake regarding the manytomany field but haven't been able to figure out the solution. Any help would be of benefit to me. TIA!
You can not save value directly to many-to-many field. Database does not allow you to do so. It only allows you to add them for associating the relationship between the two tables ( i.e User, Account ). Replace your code segment for Serializer file with the following one.
class AccountCreationSerializer(ModelSerializer):
class Meta:
model = Account
fields = ['AccountName']
class SignUpSerializer(ModelSerializer):
AccountName = serializers.SerializerMethodField()
class Meta:
model = User
fields = ['EmailId', 'AccountName', 'CreateAccount', 'password']
extra_kwargs = {'password': {'write_only': True, 'required': True}}
def validate(self, attrs):
attrs = super(SignUpSerializer, self).validate(attrs=attrs)
attrs.update({"AccountName": self.initial_data.get("AccountName")})
return attrs
def create(self, validated_data):
AccountName = validated_data.pop('AccountName')
acc = Account.objects.create(AccountName=AccountName) if "CreateAccount" in validated_data and validated_data['CreateAccount'] else None
userAcc = User.objects.create_user(**validated_data)
if acc:
userAcc.AccountName.add(acc)
return userAcc
Finally, replace your SignUpView class in the following way:
class SignUpView(APIView):
serializer_class = SignUpSerializer
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data)
is_valid_serializer = serializer.is_valid(raise_exception=True)
if is_valid_serializer:
with transaction.atomic():
serializer.save()
# Rest of your code segments to finalize the response
UPDATE
There is a problem with your create_user method. You are here passing the many-to-many field reference (AccountName), which you shouldn't. As I mentioned earlier, you can not save directly many-to-many field. You just need to associate the relation between them. Omit that and it will work!!!
Follow this new definition for this method (create_user).
def create_user(self, EmailId, CreateAccount, password):
if not EmailId:
raise ValueError("Users must have an email")
user = self.model(EmailId=self.normalize_email(EmailId), CreateAccount=CreateAccount)
user.set_password(password)
user.save(using=self._db)
return user
I know that I'm reinventing the wheel here, but I just don't like django-friendship and want to write it myself.
Thus, I have custom user model Person
class Person(AbstractBaseUser, PermissionsMixin):
username = models.CharField(('username'), max_length=75, unique=True,
help_text=('Required. 30 characters or fewer. Letters, numbers and '
'#/./+/-/_ characters'),
validators=[
validators.RegexValidator(re.compile('^[\w.#+-]+$'),
('Enter a valid username.'), 'invalid')
])
....more other fields....
friend = models.ForeignKey('self', related_name="friends", default=None, null=True)
As you can see I have foreign key with self or other words oneToMany relationship.
and here is what I have in views.py to add friends
def add_friend(request, username):
if request.user.is_authenticated():
user = Person.objects.get_by_natural_key(username)
if user is not None:
user.friend = request.user /// here
return HttpResponseRedirect('/profile/')
I'm getting user which I want to add to my friends by username and set users field(friends) to current user(request user). However, I think that I'm doing something wrong here.
I tried to google it, but didnt find example with ForeignKey('self')
At the end I did it another way
Instead of ForeignKey I used ManyToMany and class Relationship
class Person(AbstractBaseUser, PermissionsMixin):
... things here...
relationships = models.ManyToManyField('self', through='Relationship',
symmetrical=False,
related_name='related_to')
and class
class Relationship(models.Model):
from_person = models.ForeignKey(Person, related_name='from_people')
to_person = models.ForeignKey(Person, related_name='to_people')
To add user in my view I have this
def add_friend(request, username):
if request.user.is_authenticated():
user = Person.objects.get_by_natural_key(username)
Relationship.objects.get_or_create(
from_person=request.user,
to_person=user)
return HttpResponseRedirect('/profile/')
and to show list of users
def show_friends(request, username):
user = Person.objects.get_by_natural_key(username)
rel = user.relationships.filter(
to_people__from_person=user)
args = {'friends': rel}
return render(request, "profile/friend_list.html", args)
You can see more here . This post helped me understand what I'm doing wrong and what should be changed.
On my current project I want the user to be able to fill in forms without having to sign up first (to make them more likely to use the service).
On the below view I'm trying to either save the registered user with the form data, or if the user isn't registered save the Session ID as a temporary user id.
However when I try to use the session ID it returns none. I'm not sure why the data is missing? (Session have the default django setup in apps and middleware as per the docs). Note when a user is logged in it seem to have a user id but not when no user is logged in.
View:
class ServiceTypeView(CreateView):
form_class = ServiceTypeForm
template_name = "standard_form.html"
success_url = '/'
def form_valid(self, form):
if self.request.user.is_authenticated():
form.instance.user = self.request.user
else:
form.instance.temp_user = self.request.session.session_key
super().form_valid(form)
online_account = form.cleaned_data['online_account']
if online_account:
return redirect('../online')
else:
return redirect('../address')
Model:
class EUser(models.Model):
supplier1 = models.OneToOneField(SupplierAccount)
supplier2 = models.OneToOneField(SupplierAccount)
supplier3 = models.OneToOneField(SupplierAccount)
online_account = models.BooleanField()
address = models.OneToOneField(Address, null=True)
temp_user = models.CharField(max_length=255, null=True)
user = models.OneToOneField(settings.AUTH_USER_MODEL, null=True, default=None)
class SupplierAccount(models.Model):
supplier = models.ForeignKey(Supplier)
username = models.CharField(max_length=255)
password = models.CharField(max_length=255)
Form:
class ServiceTypeForm(forms.ModelForm):
# BOOL_CHOICES = ((False, 'No'), (True, 'Yes'))
# online_account = forms.BooleanField(widget=forms.RadioSelect(choices=BOOL_CHOICES))
def __init__(self, *args, **kwargs):
super(ServiceTypeForm, self).__init__(*args, **kwargs)
self.fields['service_type'].initial = 'D'
class Meta:
model = EUser
fields = ('service_type', 'online_account')
The session key will exist if there is data set in the session dictionary already. Logged in users have a session key because Django stores authentication related data in the session by default, so a key will always be assigned because of that.
You can ensure that a key always exists by tossing some data into the session storage before trying to get the key.
I am writing website and i`d like to implement profile managment. Basic thing
would be to edit some of user details by themself, like first and last name
etc. Now, i had to extend User model to add my own stuff, and email address.
I am having troubles with displaying form. Example will describe better what i
would like achieve.
This is mine extended user model.
class UserExtended(models.Model):
user = models.ForeignKey(User, unique=True)
kod_pocztowy = models.CharField(max_length=6,blank=True)
email = models.EmailField()
This is how my form looks like.
class UserCreationFormExtended(UserCreationForm):
def __init__(self, *args, **kwargs):
super(UserCreationFormExtended, self).__init__(*args, **kwargs)
self.fields['email'].required = True
self.fields['first_name'].required = False
self.fields['last_name'].required = False
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email')
It works fine when registering, as i need allow users to put username and email but when it goes to editing profile it displays too many fields. I would not like them to be able to edit username and email. How could i disable fields in form?
Thanks for help.
You should create another form, that excludes the fields you don't want (or simply don't specify them in the fields list). Then pass the 2 different forms to the registration and edit-profile views.
Try removing 'username' and 'email' from fields in Meta:
class Meta:
model = User
fields = ('first_name', 'last_name')
What i did is i created new form and used it and it worked. It allows to edit fields from User model not only UserExtended. Thanks for help.
class UserProfileForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(UserProfileForm, self).__init__(*args, **kwargs)
try:
self.fields['first_name'].initial = self.instance.user.first_name
self.fields['last_name'].initial = self.instance.user.last_name
self.fields['email'].initial = self.instance.user.email
except models.User.DoesNotExist:
pass
email = forms.EmailField(label = "Główny adres email",
help_text="",
required=True)
first_name = forms.CharField(label = "Imię",
required=False)
last_name = forms.CharField(label = "Nazwisko",
required=False)
kod_pocztowy = forms.RegexField('\d{2}-\d{3}',
required = False,
label="Kod pocztowy",
error_messages={"invalid":'Poprawna wartość to np: 41-200'})
class Meta:
model = UserExtended
exclude = ('user')
def save(self, *args, **kwargs):
u = self.instance.user
u.email = self.cleaned_data['email']
u.first_name = self.cleaned_data['first_name']
u.last_name = self.cleaned_data['last_name']
u.kod_pocztowy = self.cleaned_data['kod_pocztowy']
u.save()
profile = super(UserProfileForm, self).save(*args, **kwargs)
return profile