How to add object to a one to many relationship in django? - python

I am building a django web app which requires a one to many model relationship. I read the docs and it says to use a ForeignKey in the model field.
In my case every user needs to have a one to many field with the job model which will keep track of completed jobs by that user.
Which in django I believe is represented like so:
class User(AbstractBaseUser, PermissionsMixin):
...
job_history = models.ForeignKey(Job, on_delete=models.CASCADE)
The job model looks like this:
class Job(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="jobs")
created_at = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=30)
description = models.TextField()
pay = models.FloatField()
category = models.CharField(max_length=3, choices=JOB_CATEGORY_CHOICES)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('jobs:detail', kwargs={
'job_pk': self.id
}
)
In my view I want to add a job to the job_history one to many field. I do not know how to write this however. This is my view so far:
#login_required
def job_hire(request, account_pk, job_pk):
user = get_object_or_404(account_pk)
job = get_object_or_404(job_pk)
# I now need to save the job object in the one to many field of the user object. But how?
messages.success(request, "You have hired an applicant.")
return HttpResponseRedirect(reverse('jobs:find'))
How do I add the job to the one to many field in the user model to save the users jobs?

It is redundant that a User points to a Job and a Job points to a User as you can follow relationships backward.
The user field on the Job should be enough.
You still can lookup all jobs for a User via:
user = User.objects.first()
user.jobs.all() # shows all jobs for the user.
(Note that it would be user.job_set.all() if you hadn't set a related name to jobs)
So in your view this should be enough:
#login_required
def job_hire(request, account_pk, job_pk):
user = get_object_or_404(User, pk=account_pk)
job = get_object_or_404(Job, pk=job_pk)
job.user = user
job.save()
Actually you don't even need to fetch a User-Instance from the database but can do this:
#login_required
def job_hire(request, account_pk, job_pk):
job = get_object_or_404(Job, pk=job_pk)
job.user_id = account_pk
job.save()
Edit:
In case you want to keep both user fields and want to associate a job with a user the mechanics are still the same:
#login_required
def job_hire(request, account_pk, job_pk):
user = get_object_or_404(User, pk=account_pk)
user.job_history_id = job_pk
# or:
# job =get_object_or_404(Job, pk=job_pk)
# user.job_history = job
user.save()

I think it looks good so far. What you'll want to do is first create Job object.
job=Job(user=..., etc.)
job.save()
Then you add that job to User object.
user=User(job_history=job, ...)
user.save()

Related

Django Model: How to only access fields related to a specific user from another model

How can i only access the addresses(Address model) of specified user(User model) from Order model.
here is the code: Models.py
class User(AbstractBaseUser, PermissionsMixin):
phone_number = PhoneField(max_length=12, primary_key=True, unique=True)
class Address(models.Model):
address = models.CharField(max_length=500, blank=False,null=False,primary_key=True)
customer = models.ForeignKey((User, on_delete= models.CASCADE)
class Order(models.Model):
order = CharField(max_length=400,blank=False,null=False)
customer = models.ForeignKey(User,on_delete=models.SET_NULL, null=True)
address = models.ForeignKey(Address,on_delete=models.SET_NULL, null=True)
the address field in Order model is my problem. When creating a new order in Django Administration, when i select one of the customers, i still can choose any address registered in database
How can i limit the access to addresses to specified user.
Thanks in advance
You can not filter this in the models. You will need to do that by the form layer.
We can implement this with:
class MyForm(forms.ModelForm):
def __init__(self, *args, user=None, **kwargs):
super().__init__(*args, **kwargs)
self.fields['address'].queryset = Address.objects.filter(user=user)
class Meta:
model = Order
fields = ['address']
then in the view, we can construct a form with the logged in user as user:
from django.contrib.auth.decorators import login_required
#login_required
def my_view(request):
if request.method == 'POST':
form = MyForm(request.POST, user=request.user)
if form.is_valid():
form.instance.user = request.user
# set the order number to the instance
form.save()
return redirect('name-of-some-view')
else:
form = MyForm(user=request.user)
return render(request, 'name-of-some-template.html', {'form': form})
It depends if customers should be allowed to have multiple addresses (like in most online shops). In that case the User and Address models look pretty good!
Django Admin can be a bit tricky. But that's just the nature of the flow, because the moment you open the "Create Order Page" the server has no idea what user you will pick, and therefore does not know which address it should filter. You would have to use ajax to get to your goal, but I can propose something different...
The question is why did you add another address field to the Order? Don't get me wrong, it's the right way actually. But...
What if the user orders something, changes his address object and looks back at the order history?
Actually you COULD drop the address-foreignkey on the order and you'll still be able to access the current customer address on any order, by:
some_order = Order.objects.first()
customer = some_order.customer
# As defined in your model, one customer can have many addresses. For now just access the "latest" one
customers_address = customer.address_set.last()
But the order history would still be messy... now its even worse. Whenever the customer adds or changes the address, the order history would show wrong values.
To prevent this, you could leave the foreign key, prevent the address_id from being edited (read_only field), prevent the related address object from being edited, and add a flag if the address is visible to the user or soft-deleted.
You should do some research about read_only fields, editable and overriding model methods
But to keep things a bit more simple, lets just change the Order->address field to be a Charfield instead of a foreign key. You won't need to show an editable field inside the admin anymore and instead let the user have his default address.
class Order(models.Model):
order = CharField(max_length=400,blank=False,null=False)
customer = models.ForeignKey(User,on_delete=models.SET_NULL, null=True)
shipping_address = models.CharField(max_length=500, editable=False)
def save(self, *args, **kwargs):
# You can use this field to stringify even more complex objects
# Again, last() is not the right way in the end but you could have a specific field on the customer: preferred_address
self.shipping_address = self.customer.address_set.last().address
super().save(*args, **kwargs)

Unittest a Django model Clean method

I'm working on a simple Django social media project and I'm trying to create a unittest to verify that a User cannot like a post that they created. I want to do one of the following
Write a unittest to test a clean method of a model
or
Write a CheckConstraint to prevent a User and Post creator from being the same Can't create a Check on a related model - https://forum.djangoproject.com/t/checkconstraint-involving-related-model/5351
My Model
In my model I have a UniqueConstraint to prevent a user from liking a post more than once. I initially tried to create a CheckConstraint to prevent a user from like their own post, but couldn't figure out how to do that. An alternative I came upon was to create a clean method that achieved the same goal.
# From models.py
class Post(models.Model):
creator = models.ForeignKey(User, on_delete=models.CASCADE, related_name="creator")
content = models.CharField(max_length=160)
pub_date = models.DateTimeField('date posted', default=timezone.now)
class Like(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="likes")
user = models.ForeignKey(User, on_delete=models.CASCADE)
like_unlike = models.BooleanField(choices=BOOL_CHOICES)
def clean(self):
"""Prevent User from liking/unliking their own post"""
if self.user == self.post.creator:
raise ValidationError(_("User cannot like/unlike their own post"))
class Meta:
constraints = [
# Prevent a user from liking/unliking the same post twice
models.UniqueConstraint(fields=['user', 'post'], name="unique like"),
]
My Unittests
When creating my unittests I have been able to test the unique constraint but unable to successfully test the clean method. My expectation was that when I attempt to create a Like entry with a User the same as the Post creator that I would raise an error. I used Like.objects.create
and when that didn't work, tried assigning Like() to a variable and assigned it's values before calling save on it. However both ways seem to bypasses the clean method when it saves to the databases.
# From test_models.py
class LikeTestCase(TestCase):
def setUp(self) -> None:
User.objects.create(username="john", email="john#email.com")
user = User.objects.create(username="mary", email="mary#email.com")
Post.objects.create(creator=user, content=f"post-1", pub_date=timezone.now())
def test_cannot_like_post_multiple_times(self):
"""Verify user cannot like a post more than once"""
post = Post.objects.get(id=1)
user = User.objects.get(name="john")
Like.objects.create(post=post, user=user, like_unlike=True)
with self.assertRaises(IntegrityError):
Like.objects.create(post=post, user=user, like_unlike=True)
def test_cannot_like_own_post_1(self):
"""Verify user cannot like their own post"""
post = Post.objects.get(id=1)
like = Like()
like = Like.objects(post=post, user=post.creator, like_unlike=True)
like.save()
def test_cannot_like_own_post_2(self):
"""Verify user cannot like their own post"""
post = Post.objects.get(id=1)
Like.objects.create(post=post, user=post.creator, like_unlike=True)
From this previous answer the key was to call full_clean() on the model object. My test then became
def test_cannot_like_own_post(self):
"""Verify user cannot like their own post"""
post = Post.objects.get(id=1)
with self.assertRaises(ValidationError):
like = Like(post=post, user=post.creator, like_unlike=True)
like.full_clean()

Querying a data with Django ORM to return a specific data that belongs to a user

so I'm trying to build a Ledger App. When new users sign-ups, they create a new Business account that is linked to them (ForeignKey). Here is my model:
User = get_user_model()
class Business_Account(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
business_name = models.CharField(max_length=50)
Type_of_service = models.CharField(choices=SERVICE_CATEGORIES, max_length=50)
business_email_address = models.EmailField(max_length=254)
class Meta:
verbose_name = "Business_Account"
verbose_name_plural = "Business_Accounts"
def __str__(self):
return self.business_name
Now, I want to make a get request view that can query and returns a business account that belongs to a particular user. My current view just returns all the business accounts available in the database irrespective of which user is login. Here is my view:
class AddBusinessAcctView(APIView):
def get_object(self):
try:
return Business_Account.objects.all()
except:
raise status.HTTP_404_NOT_FOUND
def get(self,request):
queryset = self.get_object()
serializer = BusinessAcctSerializer(queryset, many=True)
return Response(data=serializer.data, status=status.HTTP_200_OK)
Now, How can I query business accounts that belong to a particular user?. Thanks in advance
Normally, you'd have authentication and a logged-in user that you were trying to retrieve information for. Assuming you're going to do this, that user is available in the request object.
Business_Account.objects.get(user=request.user)
But in general, you can just do:
user = User.objects.get(username='john')
Business_Account.objects.get(user=request.user)
# or
Business_Account.objects.get(user_id=123456)
I finally got it. It is something similar to the above answer.
queryset = Business_Account.objects.filter(user=request.user)

How to give multiple log-in's access to one keyword-based dataset in django app

Here is the scenario I am working on: I have django app that creates records which I call sessions:
blog.models.py
class Session(models.Model):
uid = models.CharField(max_length=50, blank=True)
cid = models.CharField(max_length=50, blank=True)
action_type = models.CharField(max_length=50, blank=True)
action_name = models.CharField(max_length=50, blank=True)
action_value = models.CharField(max_length=50, blank=True)
session_date = models.DateTimeField(auto_now_add=True)
client = models.CharField(max_length=50, blank=True)
I have a dashboard page to show charts and a database page to show the records as a table:
blog.urls.py
path('', auth_views.LoginView.as_view(template_name='users/login.html'), name='blog-home'),
path('<str:username>/dashboard/', views.dashboard and DashboardListView.as_view(), name='blog-dashboard'),
path('<str:username>/database/', views.database and SessionListView.as_view(), name='blog-database'),
So when you log in, my SessionListView.as_view() goes through the whole database and displays only those records where the Session.client == the url's 'username' value.
Example: when user: DummyCo logs in (www.website.com/DummyCo/database/) they see only Session records where the Session.client field is 'DummyCo.' This has worked out great so far.
But here is the problem: I now need to provide multiple logins to users to see the same dashboard and database page.
Example: jim#DummyCo.com and amy#DummyCo.com both need to see the DummyCo records, but if I provided them with their own logins then their username's in the url would not match and thus the DummyCo records would not show. I thought using the built-in django Groups would be a solution but that seems to only help with authentication and permissions on the backend. I also extended my user model with a Profile model:
users/models.py
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
group = models.ForeignKey(Group, blank=True, null=True, default=None, on_delete=models.SET_DEFAULT)
image = models.ImageField(default='default.jpg', upload_to='profile_pics')
user_client = models.CharField(max_length=50, blank=True, null=True, default=None)
def __str__(self):
return f'{self.user.username} Profile'
I made the user_client model field to try and connect the Profile (and thus User) with the Session.client field: instead of <str:username>/database/ I thought i'd be able to use <str:client_user>/database/ and simply fill that field with 'DummyCo' on both Jim and Amy's profile to give them access to the records.
I read in a couple of places that the key to handling this problem is to switch the user model from one-to-one to many-to-one type early or before i build out the app. Unfortunately I have already put a ton of work into this project. I also read that I should look at the built-in User model as more of an account and less of a user. So is there a simple way to give multiple users access to one User/account?
Also, here is the views:
blog/views.py
class SessionListView(LoginRequiredMixin, ListView):
model = Session, Profile
template_name = 'blog/database.html'
context_object_name = 'sessions'
ordering = ['-session_date']
paginate_by = 25
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Session.objects.filter(client=user).order_by('-session_date')
def get_context_data(self, **kwargs):
user = get_object_or_404(User, username=self.kwargs.get('username'))
context = super().get_context_data(**kwargs)
context['distinct_campaigns'] = Session.objects.filter(client=user).values('cid').distinct().order_by('cid')
context['distinct_action_types'] = Session.objects.filter(client=user)\
.values('action_type')\
.distinct().order_by('action_type')
return context
# login_required()
def database(request):
context = {
'sessions': Session.objects.all()
}
return render(request, 'blog/database.html', context, {'title': 'Database'})
Okay I figured out a solution:
I thought I needed to do some trickery on the html file within the for loop showing my query set sessions but it turns out that can be adjusted in my views.py file. Before this update my views.py looked like this:
class SessionListView(LoginRequiredMixin, ListView):
model = Session, Profile
template_name = 'blog/database.html'
context_object_name = 'sessions'
ordering = ['-session_date']
paginate_by = 25
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Session.objects.filter(client=user).order_by('-session_date')
def get_context_data(self, **kwargs):
user = get_object_or_404(User, username=self.kwargs.get('username'))
context = super().get_context_data(**kwargs)
context['distinct_campaigns'] = Session.objects.filter(client=user).values('cid').distinct().order_by('cid')
context['distinct_action_types'] = Session.objects.filter(client=user)\
.values('action_type')\
.distinct().order_by('action_type')
return context
I realized the def get_queryset(self) was grabbing the logged-in username, then reviewing the full database and adding all records with the same session.client value as the value of the logged in user (i.e. DummyCo). So to make this work for a user like 'DummyCo_Sally', I changed the logic in that def like so:
class SessionListView(LoginRequiredMixin, ListView):
# gets the actual user (i.e. DummyCo_Sally)
user = get_object_or_404(User, username=self.kwargs.get('username'))
# turns user to a string
user_string = str(user)
# designates the _ as the separator
sep = '_'
# strips off _ and everything after it
stripped_user = user_string.split(sep, 1)[0]
# establishes the queryset as 'DummyCo' even though 'DummyCo_sally' is logged in
return Session.objects.filter(client=stripped_user).order_by('-session_date')
I doubt this method is the best way of handling multiple users seeing one umbrella data set, but it did the trick for me. This method also likely creates a security risk for applications that have public-facing user registration. But it did the trick for me.

how to add post to django user model using foreignkey

i created a model
class ThisUser(models.Model):
created = models.DateTimeField(auto_now=True)
message = models.CharField(max_length=120)
user = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.user
I want to store message specifically for the user who is authenticated.
right now this will give me all user who is available in my user model.
Please help
user = models.OneToOneField(User, on_delete=models.CASCADE)
Instead of foriegn key use one to one relation
Well you are suppose to take care of that at view level and not model level,
for example this is how I create a new Album:
#login_required
def make_album(request):
if request.method == 'POST':
form = AlbumCreationForm(request.POST)
if form.is_valid():
new_album = core_models.Album(name=form.cleaned_data['name'], description=form.cleaned_data['description'], user=request.user)`
You can use request.user.id to get user's id for further use.

Categories