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.
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'm creating my first app with Django and still have a lot to learn, but right now I am completely stuck and need some help. I have a model for Customers and Tickets. I have it so different users can save new customers/tickets and only view their data from the dashboard once logged in. However, when creating a new ticket, there is a dropdown option to select customer for the ticket - and the current user is able to see every users customers.
Here is the code, I'll share more code if needed, but I think this covers what I have going on...
forms.py
class TicketForm(ModelForm):
class Meta:
model = Ticket
fields = ['number', 'customer','date_created','work_description','mechanics','status']
views.py
def createTickets(request):
form = TicketForm()
if request.method == 'POST':
form = TicketForm(request.POST)
if form.is_valid():
newticket = form.save(commit=False)
newticket.shopowner = request.user
newticket.save()
return redirect('tickets')
context = {
'form': form
}
return render(request, 'createticket.html', context)
models.py
class Ticket(models.Model):
def default_number():
no = Ticket.objects.count()
return no + 1
shopowner = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
number = models.CharField(max_length=30, unique=True, default= default_number)
customer = models.ForeignKey(Customer, default=1, on_delete= models.SET_DEFAULT, blank=True)
date_created = models.DateField(default=timezone.now)
work_description = models.TextField(verbose_name="Service Details: ")
mechanics = models.ForeignKey(Mechanic, default=1, on_delete=models.DO_NOTHING, blank=True, verbose_name="Mechanic")
status = models.BooleanField(default=True, verbose_name="Open Ticket")
class Meta:
verbose_name_plural = "Tickets"
I need the Customer foreignkey to only display customers of the current user (or 'shopowner') - same thing for mechanic and eventually vehicle but I can figure those out once I know how to get the customer input to display the correct data.
You'll need to customize your form a bit, in order to modify the queryset for that particular field. We also need to pass a user from the view:
forms.py
class TicketForm(ModelForm):
class Meta:
model = Ticket
fields = ['number', 'customer', 'date_created', 'work_description', 'mechanics', 'status']
def __init__(self, user=None, *args, **kwargs):
super().__init__(*args, **kwargs)
if user:
self.fields['customer'].queryset = Customer.objects.filter(shopowner=user)
views.py
def createTickets(request):
form = TicketForm(user=request.user)
# ...
Exactly how you define the queryset is going to depend on how you've defined the relationship between Customer and Shopowner, but this should give you the right approach.
I'm pretty new to Django and I am working on a project that currently requires the following:
I have two basic structures: a Project model and a TeamMember model- both related to each other through a ManytoMany relationship. Then I have an TMAssigned 'through' class. The team member will have many projects assigned to it over time.
I have a ModelFrom which creates a Project model through the creation of the form.
My question is, How do I link the team member to the newly created project upon the submission of the form?
Here is a bit of my model & form code:
TeamMember
class TeamMember(models.Model):
firstname = models.CharField(max_length=100, default= "First Name")
lastname = models.CharField(max_length=100, default= "Last Name")
fullname = models.CharField(max_length=100, default= "Full Name")
email = models.EmailField(max_length=254)
cellphone = PhoneNumberField(null=False, blank=False, unique=True)
numberofcases = models.IntegerField(max_length=10000, default=0)
#property
def fullnamefunc(self):
fullname = "{} {}".format(self.firstname, self.lastname)
return fullname
def __str__(self):
return self.fullname
Project
class Project(models.Model):
pursuitname = models.CharField(max_length=500)
datecreated = models.DateTimeField(auto_now=True)
bdmember = models.ManyToManyField('team.TeamMember')
Views.py
class bdFormView(TemplateView):
template_name = os.path.join(BASE_DIR, "templates/masterform/bdform.html")
def get(self,request):
form = bdForm()
return render (request, self.template_name, {'form': form})
def post(self, request):
form = bdForm(request.POST)
if form.is_valid():
print("form is valid")
project = form.save(commit=False)
project.save()
text = form.cleaned_data['briefcard']
Form.py
class bdForm(forms.ModelForm):
bdmemberlist = TeamMember.objects.all().order_by('lastname')
pursuitname = forms.CharField()
bdmember = forms.ModelChoiceField(queryset= bdmemberlist)
addbdteam = forms.ModelMultipleChoiceField(
queryset=TeamMember.objects.all().order_by('lastname'), widget=Select2MultipleWidget, required=False)
class Meta:
model = Project
fields = ['pursuitname','addbdteam','bdmember',]
def __init__(self, *args, **kwargs):
if kwargs.get('instance'):
initial = kwargs.setdefault('initial', {})
initial['projects'] = [t.pk for t in
kwargs['instance'].project_set.all()]
forms.ModelForm.__init__(self, *args, **kwargs)
def save(self, commit=True):
instance = forms.ModelForm.save(self, False)
old_save_m2m = self.save_m2m
def save_m2m():
old_save_m2m()
for project in self.cleaned_data['bdmember']:
instance.teammember_set.add(project)
Thanks in advance!!
Edit- after doing some more research, I've removed the "Through" model from the script and am trying to rely on the form.py save method to do the join. However, when I do this- the two are still not linking up properly.
Since only your admin (superusers?) will log in, you can start off by using the in-built Django Admin.
I would recommend this for you, at least for now, because you're a beginner and the Admin Form is stunningly simple to use. Then, you can create a custom form later on when you're more comfortable. :-)
With this in mind, you can try eliminating the 'through' table (you may need to reset your migrations), and try this.
Admin.py
from django.contrib import admin
from .models import TeamMember, TMAssigned, Project,
TeamMembersInLine(admin.TabularInline):
model = TeamMember
extra = 1
#admin.register(Project):
class ProjectAdmin(admin.ModelAdmin):
list_display = ('pursuitname', 'bdmember ', 'datecreated')
inlines = [TeamMembersInLine]
Here's another answer that delves into the through table. It was asked by someone in your situation and the answer is relevant too.
I am trying to include a search field inside my home page. It works for some of the module field. My problem is when I use a ForeignKey field (correct me please if I am wrong).
models.py
class Location(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
my_location = models.CharField(max_length=120, choices=LOCATION_CHOICES)
update_date = models.DateField(auto_now=True, null=True)
def __str__(self):
return self.my_location
class UserProfile(models.Model):
user = models.ForeignKey(User)
# The additional attributes we wish to include.
user_base = models.CharField(max_length=120, choices=LOCATION_CHOICES)
user_position = models.CharField(max_length=120)
user_phone = models.PositiveIntegerField()
def __unicode__(self):
return self.user.username
views.py
def search_by_location(request):
if 'q' in request.GET and request.GET['q']:
q = request.GET['q']
locations = Location.objects.filter(my_location__icontains=q).order_by('-update_date')
else:
locations = Location.objects.order_by('-update_date')
context = {'locations': locations}
return render(request, 'index.html', context)
My problem is if I use user inside the filter query instead of my_location I receive the error:
Related Field got invalid lookup: icontains
Please any advice on how to troubleshoot or any documentation I can read.
You can use icontains lookup on text fields. user is related (integer) field. Instead of user use user__username.
locations = Location.objects.filter(user__username__icontains=q)
class SearchView(ListView):
model = Profile
template_name = 'blog/search_results.html'
context_object_name = 'all_search_results'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
user_name = self.request.GET.get('search', '')
context['all_search_results'] = Profile.objects.filter(user__username__icontains=user_name )
return context
here is another example on how to filter objects. if searching for a user, remember to user user_username__icontains=user_name
also remember that if you use Profile your'll get a different id than if you use User
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='oser')
picture = models.ImageField(upload_to='profile_images', blank=True)
So, I've added picture to user profile , it works good , but I can't access picture to show it in user profile
view.py:
def profile(request):
if request.user.is_authenticated():
user = User.objects.get_by_natural_key(request.user.get_username())
t = loader.get_template("profile.html")
c = Context({'user2': user, })
return HttpResponse(t.render(c))
else:
raise Http404
What I've found in django docs
class Employee(models.Model):
user = models.OneToOneField(User)
department = models.CharField(max_length=100)
u = User.objects.get(username='fsmith')
freds_department = u.employee.department
However, this doesn't work for me , I just can't get UserProfile.picture
u = User.objects.get(username= request.user.get_username())
s = u.UserProfile.picture
What I'm doing wrong?
You should use "related name", which specified in foreign key field, to access user profile, not class name:
s = u.oser.picture
By default "related_name" is the same as related model class name, but in lowercase. You specified related_name="oser".