Candidate:
tim osahenru
Assessed by:
Nicholas Tollervey
Python (2023) ~ Grade 5 (Higher)
dev-locate
A platform where developers can signup with their projects on display and hiring managers can make searches of developers based on location, year of experience, tech stack or any other search query.
Attached files
Filename (click to download) | Size | Uploaded by |
---|
Screenshot_47.png | 96.8 KB | tim |
Markdown code

Screenshot_48.png | 39.2 KB | tim |
Markdown code

Screenshot_49.png | 80.1 KB | tim |
Markdown code

2022-11-28_20-06-31ipad.mp4 | 10.6 MB | tim |
Markdown code
[2022-11-28_20-06-31ipad.mp4](/media/assessment/e2c014a6/2022-11-29/15-46-55/2022-11-28_20-06-31ipad.mp4){target="_blank"}
profile.gif | 1.7 MB | ntoll |
Markdown code

project.png | 74.8 KB | ntoll |
Markdown code

homepage.png | 270.7 KB | ntoll |
Markdown code

Screenshot_57.png | 44.6 KB | tim |
Markdown code

bad.png | 50.0 KB | tim |
Markdown code

good.png | 50.1 KB | tim |
Markdown code

form.png | 20.6 KB | tim |
Markdown code

count.jpg | 739.2 KB | tim |
Markdown code

create.jpg | 712.2 KB | tim |
Markdown code

virtual.png | 34.8 KB | tim |
Markdown code

tutorial.png | 33.8 KB | tim |
Markdown code

Screenshot_88.png | 271.2 KB | tim |
Markdown code

Screenshot_86.png | 363.5 KB | tim |
Markdown code

1.png | 14.2 KB | tim |
Markdown code

2.png | 5.3 KB | tim |
Markdown code

whatsapp.png | 239.1 KB | tim |
Markdown code

received.png | 30.1 KB | tim |
Markdown code

sent.png | 31.7 KB | tim |
Markdown code

Screenshot_101.png | 160.1 KB | tim |
Markdown code

Screenshot_102.png | 23.9 KB | tim |
Markdown code

Screenshot_105.png | 41.3 KB | tim |
Markdown code

Screenshot_105.png | 41.3 KB | tim |
Markdown code

inbox_error.png | 66.3 KB | ntoll |
Markdown code

Screenshot_108.png | 37.0 KB | tim |
Markdown code

Screenshot_117.png | 27.8 KB | tim |
Markdown code

git_error.png | 42.0 KB | tim |
Markdown code

sphinx_error.png | 32.5 KB | tim |
Markdown code

sphinx_tree.png | 18.9 KB | tim |
Markdown code

readthedocs_error.png | 50.8 KB | tim |
Markdown code

complete_build.png | 298.1 KB | tim |
Markdown code

message.png | 43.5 KB | tim |
Markdown code

Status: Closed (results delivered). 88/100 ~ Pass with MERIT (19/12/2022).
tim osahenru
~ 23 Nov 2022 9:51 p.m.
The hiring process of a software developer can be tough from both the developer and a hiring manager’s point of view.
When I started my Journey as a software developer late last year (November, 2021) I had no platform to share my projects without having to build a personal portfolio. So, I thought why not build a platform where other developers can signup and display different projects they have created: have developers link their projects to the GitHub repo, live URL, a description of the project and any valuable details they choose to share about their projects.
Initially built it using Django’s function based views which is a more beginner friendly approach.
As a challenge to myself I want to implement the same idea using Django’s class based views and add some more functionality like a search functionality and chat functionality; a search function where hiring managers can search for developers using any query search and also a chat session where hiring managers can chat with any developer they choose to work with.
Why? you may ask, well as a Django developer class based view is the industry way of writing effective and efficient codes. So, to strengthen my understanding of Python and the Django framework I want to explore the world of Django using its class based views approach.
The first attempt I gave to this didn’t have much functionality to it, just a signup page, a profile settings page and a page where projects can be created, updated or deleted and lastly have all projects displayed in the homepage, but this second attempt I want to expand my technical knowledge by adding the functionalities listed above.
It's going to be fun guys... "Let the games begin"
tim osahenru
~ 23 Nov 2022 11:24 p.m.
I have created two apps (projects and accounts) within my Django project.
account app has the models.py
file where I have created a model for our engineers as shown below
accounts.models
from django.db import models
from django.contrib.auth.models import AbstractUser
class Engineer(AbstractUser):
username = models.CharField(max_length=200, null=True)
email = models.EmailField(unique=True, null=True)
bio = models.TextField(default='little about you...')
# avatar =
country = models.CharField(max_length=200)
years_of_experience = models.PositiveIntegerField(null=True, blank=True)
tech_stack = models.CharField(null=True, blank=True, max_length=300, default='stack_one | stack_two | stack_three')
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
def __str__(self):
return self.username
tim osahenru
~ 23 Nov 2022 11:31 p.m.
(updated: 24 Nov 2022 9:14 a.m.)
Similarly, in our projects module we also have a models.py
where we create a model for our projects
projects.models
from django.db import models
from accounts.models import Engineer
class Project(models.Model):
engineer = models.ForeignKey(Engineer, on_delete=models.CASCADE)
name = models.CharField(max_length=100, default='Max 100 char')
tech_used = models.CharField(max_length=200, default='stack_one | stack_two')
updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
make_public = models.BooleanField(default=False)
# image =
description = models.TextField(null=True, blank=True)
repo_url = models.URLField(null=True, blank=True)
live_url = models. URLField(null=True, blank=True)
def __str__(self):
return self.name
class Meta:
ordering = ['created']
avatar and image field where commented out: Django image field requireas a bit of modification before they can be used, this will be visited later
Next, we build a CRUD functionality using class based views
tim osahenru
~ 24 Nov 2022 11:08 a.m.
(updated: 25 Nov 2022 8:43 p.m.)
The beauty in building with Django’s class based views is you write cleaner codes which are reusable and horror will be that there are so many functions operating under the hood which are not comprehensible to a beginner.
Query our project model by displaying all projects that has the make_public
field set to True
# ............. All Projects ...............
class AllProjects(ListView):
model = Project
context_object_name = 'projects'
template_name = '../templates/index.html'
def get_queryset(self):
public_projects = Project.objects.filter(make_public=True)
return public_projects
Get more details of each projects
# ............. Detail Project ...............
class ProjectDetail(DetailView):
model = Project
template_name = '../templates/detail.html'
Edit each project and redirect user back to the all projects once successful
# ............. Edit Project ...............
class ProjectEdit(LoginRequiredMixin, UpdateView):
model = Project
template_name = '../templates/edit.html'
fields = '__all__'
success_url = reverse_lazy('all_projects')
Create each project by assigning the logged in user to the project they have just created and once successful redirect the user to all projects
# ............. Create Project ...............
class ProjectCreate(LoginRequiredMixin, CreateView):
model = Project
template_name = '../templates/create.html'
form_class = ProjectForm
success_url = reverse_lazy('all_projects')
def form_valid(self, form):
if form.is_valid():
form.save(commit=False)
form.instance.engineer = self.request.user
form.save()
return redirect('all_projects')
return super(ProjectCreate, self).form_valid(form)
# ............. Delete Project ...............
class ProjectDelete(LoginRequiredMixin, DeleteView):
model = Project
template_name = '../templates/delete.html'
fields = '__all__'
success_url = reverse_lazy('all_projects')
Next will be to create an authentication system where engineers can signup, create an account, add a project, ...
tim osahenru
~ 25 Nov 2022 8:46 p.m.
Hey whatsup guys....
So I modified my CRUD function; a LoginRequiredMixin
has been added to ensure unauthenticated users are not able to create, edit or delete projects but do not require any authentication to view projects
tim osahenru
~ 25 Nov 2022 8:56 p.m.
Also moving on we have been able to also add an authentication system where Engineers can SignUp and Login to create a project
As a thing of preference I used email and password to login users as opposed to a username and password; I assume users easily remember their email address which is a unique id as to a username
# ................ Login User ........................
class LoginUser(LoginView):
model = Engineer
redirect_authenticated_user = True
template_name = '../templates/login.html'
Once an Engineer has successfully created an account they redirected to a login page, if the login is successful they are redirected to home page
NOTE TO SELF: Users should be redirected to their profile page after login instead of the home page
# ............................. SignUp User ....................
class UserSignUp(FormView):
form_class = EngineerCreationForm
template_name = '../templates/signup.html'
success_url = reverse_lazy('login')
def form_valid(self, form):
if form.is_valid():
form.save()
return redirect('login')
return super(UserSignUp, self).form_valid(form)
tim osahenru
~ 25 Nov 2022 9:04 p.m.
Visitors(Hiring managers) can now visit the profiles of the Engineers, they find more about them through their profile and also chat them up.
# ....................... Engineer Profile .....................
class EngineerProfile(DetailView):
model = Engineer
template_name = '../templates/profile.html'
NB. The chat function hasn't been added yet
tim osahenru
~ 25 Nov 2022 9:11 p.m.
To have a clean design, Engineers should have all their projects in their profile where they can either update or delete the projects... On a second thought too, I think I should also add the create function to the Engineer's profile...
hmm... thinking!
tim osahenru
~ 25 Nov 2022 9:34 p.m.
Challenge:
Snug! 😩 I'm having a bug in my code
from the project details page I am trying to take visitors to the profile page of the engineer when they click on a checkout my profile
button
Line 9 shows something is messed up with my code.
thinking in process... 😓😓
tim osahenru
~ 25 Nov 2022 10:15 p.m.
Eureka! 😆😆😆
So I figured out I wasn't passing my query properly in the templates.
this is how I passed it engineer.id
Recall from our Project models that is projects.models
we tied a relationship(ForeignKey) with every engineer so to fix the bug I just queried upwards using project.engineer.id
in my templates
tim osahenru
~ 25 Nov 2022 10:38 p.m.
Cool! all the pieces are coming together. Next, is to query individual projects of an engineer present so it is available on their profile. The edit and delete button will only be visible to the engineers who created the projects and also only them can edit or delete a project, so you can't delete or delete what you didn't create
tim osahenru
~ 26 Nov 2022 1:29 a.m.
(updated: 26 Nov 2022 1:32 a.m.)
I just faced another bug which I fixed, hilariously I don't understand why it worked so I'll just included both lines of codes.
Here the line of code of with the bug issue
# ....................... Engineer Profile .....................
class EngineerProfile(DetailView):
model = Engineer
template_name = '../templates/profile.html'
def get_context_data(self, **kwargs):
context = super(EngineerProfile, self).get_context_data()
context['engineer'] = Engineer.objects.get(id=pk)
context['public_projects'] = context['engineer'].project_set.filter(make_public=True)
context['private_projects'] = context['engineer'].project_set.filter(make_public=False)
return context
error message I got
added this line of code
pk = self.kwargs.get('pk')
# ....................... Engineer Profile .....................
class EngineerProfile(DetailView):
model = Engineer
template_name = '../templates/profile.html'
def get_context_data(self, **kwargs):
context = super(EngineerProfile, self).get_context_data()
pk = self.kwargs.get('pk')
context['engineer'] = Engineer.objects.get(id=pk)
context['public_projects'] = context['engineer'].project_set.filter(make_public=True)
context['private_projects'] = context['engineer'].project_set.filter(make_public=False)
return context
Not sure why I had to declare pk
as a variable I thought pk
by default is passed into a DetailView
please I need clarity on this
tim osahenru
~ 26 Nov 2022 9:03 p.m.
Hello guys, another functionality has been added, Engineers can now update their profiles from the profile page.
# ............. Engineer Settings ........................
class EngineerSettings(LoginRequiredMixin, UpdateView):
model = Engineer
template_name = '../templates/settings.html'
form_class = EngineerForm
def form_valid(self, form):
if form.is_valid():
form.save()
return redirect('profile', pk=self.request.user.id)
return super(EngineerSettings, self).form_valid(form)
tim osahenru
~ 28 Nov 2022 8:44 p.m.
(updated: 29 Nov 2022 3:50 p.m.)
Here is a link to the github repo of the entire project
tim osahenru
~ 29 Nov 2022 3:42 p.m.
(updated: 29 Nov 2022 10:18 p.m.)
Here is a video that highlights some of the major functionalities of the application.
Nicholas Tollervey
~ 03 Dec 2022 6:39 p.m.
(updated: 03 Dec 2022 11:05 p.m.)
Tim, 👋
First of all, huge congratulations on the magnificent work done so far 🎉.
I'm going to be your mentor and, in addition to my role as assessor for your grade, I'm here to help you develop and refine your project. If at any point you have any questions, comments or require more guidance, please don't hesitate to ask here on CodeGrades. In this way the story of our collaboration on your project will unfold as evidence of your attainment.
I have some initial feedback, and please don't be disappointed by the amount of detail... this is to be expected as we start exploring your project, and it reflects the relatively advanced level of grade 5.
The important thing is for you to read and reflect then refine your project. Like I said, I'm here to help, and part of that help is to suggest ways to improve and change the project. You're also quite within your rights to disagree with me or decide to take my suggestions in an unexpected direction, so long as you back yourself up and explain what you're doing..! 👍
Developer Setup
I've looked at your source code on GitHub, cloned it so I have a local copy and tried to get it to run.
I ran into some problems, which you may wish to address.
It's usually good practice to have a "Developer Setup" section in the README to give baby-steps to a working local development environment. How do you know what to write in such a "Developer Setup" section? Well... try cloning your repository and using a clean virtual environment to see what happens, as if you were a new developer on the project. 😄 In any case, here's what I found:
- It's not clear how to get the project to run locally. Happily, I've been using Django for 15+ years, so I knew what to do, but imagine I had never used Django before... eventually I may have figured out to go read the Django project's docs, but having some clear "baby steps" instructions for what to type to get things running would save time and is a conscientious thing to do for fellow developers or new-joiners to a project.
- Clearly I have to setup the database... I checked
settings.py
to see you were using sqlite, and then tried tomigrate
. - I hit the first error:
django.core.exceptions.ImproperlyConfigured: Set the SECRET_KEY environment variable
- You have
SECRET_KEY = env('SECRET_KEY')
insettings.py
. You don't mention in the README that I need to create such an environment variable. Your task: in the README, explain how to set this up properly. 👍 - Once this was fixed, I got yet a further error when I tried to migrate:
SystemCheckError: System check identified some issues:
ERRORS:
accounts.Engineer.avatar: (fields.E210) Cannot use ImageField because Pillow is not installed.
HINT: Get Pillow at https://pypi.org/project/Pillow/ or run command "python -m pip install Pillow".
projects.Project.image: (fields.E210) Cannot use ImageField because Pillow is not installed.
HINT: Get Pillow at https://pypi.org/project/Pillow/ or run command "python -m pip install Pillow".
- The
HINT
at the bottom of the error message told me I needed to installpillow
. Clearly this was missing from the "Install Requirements" part of the README. 😉 Your task: you have three requirements: Django itself, and the packagesdjango-environ
andpillow
. I think it'd be helpful if you knew about the Python convention forrequirements.txt
(this article is a good explanation of the basics). Can you add arequirements.txt
file to the repository, then update your README to explain how to use a virtual environment and the requirements.txt as the means of installing the project's dependencies..? - Once I'd installed
pillow
themigrate
command worked, and I was able torunserver
and see your website running on my local machine. Hurrah 🎉.
In summary, I was able to get your project running, but it took a bit of work and specialist knowledge. Please can you update your README with the baby-steps needed to set up a development version?
Running the project
Once I got the project running I noticed an error in the output from the local server:
Internal Server Error: /project.png/
Traceback (most recent call last):
File "/home/ntoll/.virtualenvs/dev-locate/lib/python3.8/site-packages/django/db/models/fields/__init__.py", line 2018, in get_prep_value
return int(value)
ValueError: invalid literal for int() with base 10: '.png'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/ntoll/.virtualenvs/dev-locate/lib/python3.8/site-packages/django/core/handlers/exception.py", line 55, in inner
response = get_response(request)
File "/home/ntoll/.virtualenvs/dev-locate/lib/python3.8/site-packages/django/core/handlers/base.py", line 197, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/ntoll/.virtualenvs/dev-locate/lib/python3.8/site-packages/django/views/generic/base.py", line 103, in view
return self.dispatch(request, *args, **kwargs)
File "/home/ntoll/.virtualenvs/dev-locate/lib/python3.8/site-packages/django/views/generic/base.py", line 142, in dispatch
return handler(request, *args, **kwargs)
File "/home/ntoll/.virtualenvs/dev-locate/lib/python3.8/site-packages/django/views/generic/detail.py", line 108, in get
self.object = self.get_object()
File "/home/ntoll/.virtualenvs/dev-locate/lib/python3.8/site-packages/django/views/generic/detail.py", line 37, in get_object
queryset = queryset.filter(pk=pk)
File "/home/ntoll/.virtualenvs/dev-locate/lib/python3.8/site-packages/django/db/models/query.py", line 1420, in filter
return self._filter_or_exclude(False, args, kwargs)
File "/home/ntoll/.virtualenvs/dev-locate/lib/python3.8/site-packages/django/db/models/query.py", line 1438, in _filter_or_exclude
clone._filter_or_exclude_inplace(negate, args, kwargs)
File "/home/ntoll/.virtualenvs/dev-locate/lib/python3.8/site-packages/django/db/models/query.py", line 1445, in _filter_or_exclude_inplace
self._query.add_q(Q(*args, **kwargs))
File "/home/ntoll/.virtualenvs/dev-locate/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1532, in add_q
clause, _ = self._add_q(q_object, self.used_aliases)
File "/home/ntoll/.virtualenvs/dev-locate/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1562, in _add_q
child_clause, needed_inner = self.build_filter(
File "/home/ntoll/.virtualenvs/dev-locate/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1478, in build_filter
condition = self.build_lookup(lookups, col, value)
File "/home/ntoll/.virtualenvs/dev-locate/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1303, in build_lookup
lookup = lookup_class(lhs, rhs)
File "/home/ntoll/.virtualenvs/dev-locate/lib/python3.8/site-packages/django/db/models/lookups.py", line 27, in __init__
self.rhs = self.get_prep_lookup()
File "/home/ntoll/.virtualenvs/dev-locate/lib/python3.8/site-packages/django/db/models/lookups.py", line 341, in get_prep_lookup
return super().get_prep_lookup()
File "/home/ntoll/.virtualenvs/dev-locate/lib/python3.8/site-packages/django/db/models/lookups.py", line 85, in get_prep_lookup
return self.lhs.output_field.get_prep_value(self.rhs)
File "/home/ntoll/.virtualenvs/dev-locate/lib/python3.8/site-packages/django/db/models/fields/__init__.py", line 2020, in get_prep_value
raise e.__class__(
ValueError: Field 'id' expected a number but got '.png'.
[03/Dec/2022 16:45:32] "GET /project.png/ HTTP/1.1" 500 137493
Clearly the site still works, but there appears to be a problem somewhere. Can you take a look and tell me what you find..? Thank you..! 🤔
It was very easy to sign up to my locally running version. 💥 When I tried to update my profile I found the form floated to the bottom of the screen. Also, the tech-stack list feels like it should be a comma separated list. I've attached a screen-grab of this process so you can see what I mean.
I notice the default content for blank fields doesn't get automatically replaced when you type. There's a trick you can use in the HTML for the <input>
elements in your form. Just set, the placeholder
attribute like this: <input placeholder="A default value" type="text"/>
. It's a little thing, but will make your site feel nicer to use. 👍
When I click on "My projects", I clearly don't have any projects... so I was expecting to see an "Add Project" button somewhere. It took me a while to find it (it's on the homepage). Although I have to say, it's really fun to see my project on the website (it looks really professional!).
There are certain aspects of the site that clearly don't (YET!) work. You also use dummy data or placeholder content. This is absolutely NOT A PROBLEM, and helps me to understand how you'd like the site to develop. Really great work. But, since you've hinted at features, I'd like to see these functioning if at all possible. This screen shot (with annotations) describes what I mean:
- Can we search by username or tech stacks?
- The shortcuts don't work, and the numbers are dummy data. Can you fix this so I have an inbox and the data is live..?
- Replace dummy data with live data.
- Broken profile image. Can you fix this..?
- Once I have an inbox, I assume new messages will appear in "Recent Activities"..? Or perhaps this is for social media activity..? A Twitter stream perhaps..?
Related to point 2. Can I also send a message to another developer..? I think with the search and messaging working (along with those other bug fixes, and live real data) you'll have a really useful and professional looking website. 🚀
Code related matters
I've taken a look around your code and have a few more items of feedback:
- I notice the commented out
HiringManager
along with theEngineer
model for representing a user's account. Can you finish this off and allow the user to define what sort of person they are (hiring manager or engineer)..? I think that would be really helpful. Alternatively, perhaps it's just simpler if all users are just "normal" users and, depending on a flag set on the user object, they appear as an engineer or hiring manager..? What do you think..? Your task: decide on an approach and update the code (hint: you may also need to create new database migrations). - I notice in your
views.py
that your classes have comments like this:
# ............. All Projects ...............
class AllProjects(ListView):
model = Project
context_object_name = 'projects'
template_name = '../templates/index.html'
def get_queryset(self):
public_projects = Project.objects.filter(make_public=True)
return public_projects
The convention in Python is to write comments for classes and methods inside triple quoted strings, underneath (not above) the thing you're commenting about. Like this:
class AllProjects(ListView):
"""
All projects listed on a single page.
"""
model = Project
context_object_name = 'projects'
template_name = '../templates/index.html'
def get_queryset(self):
public_projects = Project.objects.filter(make_public=True)
return public_projects
Also, something for you to think about... in the example code immediately above, there is a get_queryset
method. In your code you have two lines, but how might you make it just one line..? (Hint: a version with just one line would start with return
). I'm asking this because it's always good to write less code if possible, unless more code makes it understandable. Don't hesitate to ask if this question isn't clear. 👍
- There are no tests 😱. One of the core concepts for grade 4 (the one before the grade you're taking), is an understanding of unit testing. This is definitely something I (and anyone hiring engineers) will want to see. The important thing to remember is only to test the functionality you write, not the behaviour of Django. So, for instance, you could write tests for the
Project
model to check that yourimage_url
property behaves as expected both when there is an image and when there is not. But you don't have to check the way the rest of the model works, because that's just Django related stuff. If you're not sure where to start with testing, Django's own documentation on testing is excellent, and I heartily recommend The Goat Book about testing with Python and Django. - You may want to check your "style" of Python against PEP8. It helps make sure you're writing and laying out your code in a consistent way, that's easy for others to understand. Furthermore, tools like black will automagically reformat your code to "look good". ✨ These sorts of tools and standards are common in professional coding situations, so it's good to know about them and practice using them.
- Documentation: I've already mentioned the README earlier. You should also add explanations of how the code base is organised (for instance, what each of the apps in your website does), how to run the test suite, any coding standards you use (PEP8 / black?) and anything else a technical user might need. In addition to the README (for developers), you might want to add a "help" or "tutorial" section to the website itself... these aspects of documentation being for the users of the website (hiring managers and candidate engineers). 📖
- Finally, as I was poking around the code I made some changes and edits as experiments. When I typed
git status
I saw a bunch of untracked.pyc
files. Can you modify your.gitignore
file to ignore these please..? (A quick Google for "Python .gitignore" should point you in the right direction for what you need to do, and why.)
Once again, huge congratulations on getting such an impressive first draft of your project ready for feedback. I really enjoyed reviewing and engaging with your project so far, and I'm looking forward to watching and interacting with you as you refine, fix, improve and extend it. You've lots to do, and I'm excited to see how this progresses.
Onwards... 🚀
tim osahenru
~ 05 Dec 2022 11:21 a.m.
Hello Nick,
Thank you for your detailed feedback
I'm making changes to the feedbacks I understand and to the ones I don't I'm pointing them out to you.
I have been able to update my README
file by adding a Delevoper setup section and also updated my install requirements as shown below
tim osahenru
~ 05 Dec 2022 11:32 a.m.
The first error you encounter when running the application is a syntax error where omitted .url
from my image src
as shown below
Error is seen on line 63
Fixed error
tim osahenru
~ 05 Dec 2022 11:36 a.m.
(updated: 05 Dec 2022 12:01 p.m.)
As for the floated form I notice the position of the form changes based on screen size I'm still figuring out to make in fixed on all screen size, frontend isn't my strong suit.
However, I have included a placeholder
to all forms where necessary as shown below
tim osahenru
~ 05 Dec 2022 7:16 p.m.
(updated: 10 Dec 2022 4:05 p.m.)
NEW FUNCTIONALITIES
You can now make use of the search bar by making a search query with either the country, name of an engineer or a tech stack.
Also the number of Engineers on our platform can also be viewed from the homepage
Added a create button also to the projects page
Nicholas Tollervey
~ 06 Dec 2022 5:35 p.m.
Hi Tim,
It's great to see you making progress. Here's some more feedback. 👍
- For me to be able to see your changes, you should push your code to GitHub. The project repository still says it was updated 8 days ago -- the recent fixes do not appear.
- I thought it might be a missing
.url
causing the error... something I've done many times myself. 😉 - It's good to see a "developer setup" section and additional instructions in the
README
. 🎉 One of the core concepts needed for this grade is an appreciation of virtual environments, and describing how to use them to set up a development environment in theREADME
is a good opportunity for you to demonstrate this. A good introduction to Python virtual environments can be found here. - It's great to see search and other features added to the site. These steps make me feel like things are coming together.
- Just a polite reminder about adding tests. 😀
- Have you thought about hosting the website somewhere online..? The folks at PythonAnywhere have a free tier for learners that I think you could use for free Django hosting.
I'll give more feedback when you let me know the code is up on GitHub. 🚀
tim osahenru
~ 06 Dec 2022 6:22 p.m.
Hello Nick, thank you for keeping track of my project progress I have pushed the latest changes to the project repo on github
I have included pip install black
to my README file and also gitinore
all .pyc
files and relative pycaches
as for this line of code
def get_queryset(self):
public_projects = Project.objects.filter(make_public=True)
return public_projects
not sure how to go about the feedback you gave, however, for me having the two lines of codes makes it readable for me and I assume any developer(newbies especially).
Nicholas Tollervey
~ 06 Dec 2022 7:22 p.m.
Hi Tim,
Let's imagine a metaphor: there is a student and a teacher. The student asks the teacher a question, "what is the capital city of Ghana?" and the teacher writes the answer on a slip of paper, puts it into an envelope labeled "the capital city of Ghana" and then hands it to the student. Surely the teacher could just reply with the answer..! 🇬🇭 😄
That's a little bit like your function. Calling it is like the student asking a question, and inside the function the teacher is wrapping the answer in the public_projects
envelope.
Why not just return the result like this..?
def get_queryset(self):
return Project.objects.filter(make_public=True)
Of course, you could say that public_projects
is helpful because its name describes what it contains. But I'd argue that Project.objects.filter(make_public=True)
is equally as revealing, so the extra "envelope" of public_projects
isn't needed.
Clearly this is a question of taste and preference, and please don't think that you'll be penalised for doing it one way or another. But I just wanted to highlight that, in general, less code is better unless this makes the code harder to understand.
Does this help..?
Keep up the engagement, refinement and additions to the project. 🚀
tim osahenru
~ 07 Dec 2022 3:51 a.m.
...It helps alot Nick, thank you for the insight. Honestly your point became clearer with the illustration given I've however made this change
I have updated the README file with a step by step guide on how to setup a virtual environment on a python project
also added to the README file is a mini tutorial on how to use dev-locate as an Engineer and also a Hiring manager
tim osahenru
~ 07 Dec 2022 5:06 a.m.
I'm currently working on two features(function and code related); a messaging functionality and a test script for image_url
, when I am done I really will love to host this project on the suggested platform.
I am also rethinking my design choice, as you mentioned, rather than having a HiringManager
model I'll just let every user sign up as an Engineer and then differentiate each object with a flag, though I'm not sure what that flag could be, thanks for these insights Nick.
Nicholas Tollervey
~ 07 Dec 2022 3:53 p.m.
Hi Tim,
It sounds like you're working on the updates to the project in the right sort of a way.
Just a few pointers to keep in mind:
- Simple is good.
- Naming things is important so the code reads in an understandable way.
- The flag for users could be a multiple choice field (see the Django docs for how) that sets the user's status to one of: "I'm looking for work", "I'm hiring", or "I'm employed". There are probably better choices, but I'll leave that for you to decide. 😄
- I can't stress enough the importance of tests. 💥
A quick heads up. I think I'll be in a good position to provide your final assessment for the grade when I see the following things (note the new features requested):
- Tests. 😄
- New request: User documentation created via Sphinx (ask if you're unsure - I'm more than happy to point out resources).
- New request: A way for a user to download their data, as a JSON based data dump (hint: read this).
- The site hosted somewhere online (say, via PythonAnywhere or similar).
- New request: A description of user testing (ask some friends to give the site a try out, describe how they found it, reveal where things went well, and what you did to address any problems they might have encountered)
- Comments / evidence from you about each of the five prior items.
I've mentioned the new requests now, because I didn't want to overwhelm you with stuff to do when I sent over my initial feedback. 👍
But given that you've responded to my points and progress is going well, I wanted to share with you those last things that will get this project over the line... and so you get the best possible mark that reflects your level of attainment while also giving you a portfolio project you can show to potential employers (who'll be able to see our conversation and evidence of the way you engage as a software engineer).
The assessment part of the grading process is described here and the assessment criteria mentors use to calculate the final mark is found here.
As always, don't hesitate to ask questions, and keep up the good work. 🚀
tim osahenru
~ 08 Dec 2022 8:51 p.m.
Hello Nick,
Thanks for the effort you've been putting to mentor me on this project it's been mind blowing, I have had to work with some new technology like Sphinx.
So, I have to put on hold the messaging feature I was working on to focus on the core assessments for this Grade
below is the code for the test I am trying to run, I am having a challenge trying to mock an image for my test.py
from django.test import TestCase
from .models import Project
from django.core.files.images import ImageFile
from io import BytesIO
class TestModels(TestCase):
def setUp(self):
new_image = BytesIO()
self.image = Project.objects.create(
image=ImageFile(new_image)
)
def test_image_url(self):
self.assertEquals(self.image.image_url, '')
tim osahenru
~ 08 Dec 2022 9:07 p.m.
Below is a snapshot of my documentation using Sphinx
I really had FUN using it for the first time, a few question/observations
- Besides this style of documentation been available to the public what differentiates it from having a traditional README file?
- How do I write a one line code block using Sphinx like in a markdown I can do this using double backtick.
- I also tried to take my documentation journey a step further by trying to push my docs to readthedocs.org but, I keep getting this error when I try to build
tim osahenru
~ 08 Dec 2022 9:16 p.m.
Users can be able to extract their data in JSON format from their profile by clicking the extract data
button as shown below
giving an output like this
tim osahenru
~ 08 Dec 2022 9:23 p.m.
As regards the users experience with this project I have 2 sets of individual testing out this project one an experienced developer and the other a novice, just to properly cover all areas I might have skipped while setting up my documentation, I will also love to deploy this project on the suggested platform but I guess I have to be done with these key pointers first.
Thanks Nick.
tim osahenru
~ 09 Dec 2022 5:22 p.m.
User experience While some users found it easy setting up their environment and cloning the project because of their relative programming experience, others found it quite difficult so, I had to include more details to the documentation on how to get the project up and running on a local machine.
User one
as a result of this feedback I had to include some basic git syntax, initially assumed every user will be familiar with git.
Also added to the documentation a guide on how to get Python and Git running on your machine.
User two An experienced developer gave a more resourceful feedback on the project as shown below
and I also had to explain the decision behind my design choices as shown below
tim osahenru
~ 10 Dec 2022 12:26 a.m.
Hello Nick, I'm trying to test the waters by deploying my project on pythonanywhere but I keep getting this error not sure why, it seems my index file is missing but it still runs perfectly on my local machine.
tim osahenru
~ 10 Dec 2022 4:51 a.m.
(updated: 10 Dec 2022 4:53 a.m.)
Fixed!!!
So, apparently I was pointing to the wrong folder in the working directory
This web application is available for preview at
Hurray!!!
tim osahenru
~ 10 Dec 2022 2:48 p.m.
(updated: 10 Dec 2022 4:11 p.m.)
Messaging feature
I'm adding a messaging feature to this project where authenticated users can send messages.
Go to a user's profile click message and drop a message for them, when the user logs in they can see the message and you too can also see the message sent to your inbox, the challenge I'm having is the receiver's inbox is still blank so when they log in they don't see any messages although the SENDER sees theirs in their inbox
below is my code
models.py
class Inbox(models.Model):
engineer = models.ForeignKey(Engineer, null=True, on_delete=models.CASCADE, related_name='engineer')
sender = models.ForeignKey(Engineer, null=True, on_delete=models.CASCADE, related_name='mail_sender')
receiver = models.ManyToManyField(Engineer, related_name="mail_receiver")
body = models.CharField(max_length=500)
updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["-created"]
def __str__(self):
return self.sender.username
views.py
class CreateMessage(LoginRequiredMixin, CreateView):
model = Inbox
template_name = "../templates/create-message.html"
fields = ["body"]
def get_context_data(self, **kwargs):
context = super(CreateMessage, self).get_context_data()
pk = self.kwargs.get('pk')
context['engineer'] = Engineer.objects.get(id=pk)
context['mails'] = Inbox.objects.filter(sender=self.request.user)
return context
def form_valid(self, form):
if form.is_valid():
form.save(commit=False)
form.instance.sender = self.request.user
form.save()
return redirect("create-message", pk=self.request.user.id)
return super(CreateMessage, self).form_valid(form)
tim osahenru
~ 11 Dec 2022 5:48 p.m.
(updated: 11 Dec 2022 5:55 p.m.)
Update on writing test
So, I got confused with the concept of writing a test for my image_url
after much research and study I think I figured it out here.
class ProjectImageUploadTest(TestCase):
def setUp(self):
self.engineer = Engineer.objects.create(username='Engineer')
self.project = {
'engineer': self.engineer,
'name': 'Test project',
'tech_used': 'Test tech',
'updated': timezone.now(),
'created': timezone.now(),
'image': None,
'description': 'Test description',
'repo_url': 'https://github.com/TimOsahenru/dev-locate',
'live_url': 'http://timosahenru.pythonanywhere.com/',
'make_public': False
}
self.obj = Project.objects.create(**self.project)
def test_upload_image(self):
self.assertEquals(self.obj.image_url, '')
Earlier I noticed I noticed when a user creates a project without any image it gave an error, two fix to this error as shown below
One, was to provide a default image for all projects image = models.ImageField(default="project.png")
two, was to provide an object without an image by creating a function as shown below
@property
def image_url(self):
try:
url = self.image.url
except:
url = ""
return url
class Meta:
ordering = ["-created"]
The test checks out if my second fix is valid and my test results agrees as shown below also
tim osahenru
~ 13 Dec 2022 4:43 p.m.
(updated: 13 Dec 2022 4:45 p.m.)
Update Messaging function
When I try to establish a relationship between my Engineer models and the Inbox models I'm getting this error message not sure what to make of it, I think it says I can't establish the relationship like that but don't know why
Inbox models.py
class Inbox(models.Model):
sender = models.ForeignKey(Engineer, null=True, on_delete=models.CASCADE, related_name='mail_sender')
receiver = models.ForeignKey(Engineer, null=True, on_delete=models.CASCADE, related_name="mail_receiver")
message = models.CharField(max_length=500)
updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["-created"]
def __str__(self):
return self.sender.username
Engineer models.py
class Engineer(AbstractUser):
username = models.CharField(max_length=200, null=True)
email = models.EmailField(unique=True, null=True)
bio = models.TextField()
avatar = models.ImageField(default="profile.png")
country = models.CharField(max_length=200)
years_of_experience = models.PositiveIntegerField(null=True, blank=True)
tech_stack = models.CharField(null=True, blank=True, max_length=300)
inbox = models.ForeignKey(Inbox, null=True, on_delete=models.CASCADE)
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["username"]
def __str__(self):
return self.username
Nicholas Tollervey
~ 13 Dec 2022 5:42 p.m.
Hi Tim,
Apologies for my slow response time, I've had a cold but feeling much better now. Also, I continue to enjoy reading your updates about the project and appreciate all the work you've been putting in. It feels like it's really coming together.
You have asked several questions, and here are my answers:
- What's the difference between a README and Sphinx style docs? I'd say the README is more like a summary to get folks started. Furthermore, the README is usually only for developers who need to work with the code. The Sphinx style docs can also be technical in nature, but may also include user documentation too. Furthermore, Sphinx allows you to create quite sophisticated collections of documentation that can be far better or easier organised than, say, a single README file. If you're interested in exploring documentation further I heartily recommend starting with the folks in the WriteTheDocs community who have created and maintain all sorts of useful resources for folks who need to write on technical subjects, be that for developers or users.
- You ask about formatting code in Sphinx. There are two answers, tell Sphinx to use Markdown (as used on CodeGrades and many other places... these instructions to tell Sphinx to use Markdown should help), or alternatively you can learn to use
reStructuredText
(the default markup language used by Sphinx... this excellent cheatsheet reveals the core syntax). I think the answer you seek is in the "Blocks" section of the cheatsheet. - The error you see on ReadTheDocs is probably because the version of Python they use is not as up-to-date as the version you're using. I think you can probably change this in your ReadTheDocs control panel. The RtD team also have a lot of documentation and guidance about this sort of thing... so I imagine a quick Google will turn up a solution. In any case, it's really great to see you taking the initiative here and keep this up.
I really appreciated you sharing your experiences with user feedback. I can't stress how important this is - especially for web development since we, as developers, inevitably make so many assumptions based on our own (rather than our user's) experiences. User feedback ensures we're solving their problems or helping them with their situations, rather than imagining something else based on our own limited experience. It's actually quite humbling yet really very useful. 👍
Seeing your work on PythonAnywhere put a huge smile on my face. This is an important step... not only is it important to create things with your code, you should also share them... and that in itself is often a technical journey of discovery (as you found with the issue about the working directory configuration). By the way, I think PythonAnywhere are a great example of a website / service that has really great documentation, and somewhere from which you can draw inspiration for your own docs.
I tried to have a go with the new messaging functionality, but alas, encountered a problem (see the error message below):
Without looking at the code, I'm not sure what's going on. But by the looks of it I don't think the expected mailbox is getting created in the database when the user is also created. Could you take a look and fix it please..?
Finally, great work on overcoming the issues with the test. To add some context about testing here, the CodeGrades website itself is written in Django and has several hundred tests. Things are configured in such a way that the website won't deploy/update unless all the tests pass. If I encounter a bug, I usually write a test to recreate the bug, and then fix it by making the test pass. In that way, I can be certain I never re-introduce problem behaviour into the code. This is perhaps the most useful thing tests bring to our code: the confidence to be able to make changes knowing that the test suite will fail if we mess up. 😄
It feels like the project is very close to a really good state. From my perspective I think you have three tasks ahead of you (all mentioned above):
- Docs on ReadTheDocs.
- Messaging fixed.
- All updated on PythonAnywhere.
Onwards. 🚀
tim osahenru
~ 15 Dec 2022 1:34 a.m.
Hello Nick, so sorry to hear about your health condition and I'm happy to hear you're much better and thanks for the review and shading light on the questions asked.
UPDATE ON MESSAGE FUNCTION So the reason for the error I initially highlighted while trying to create a message function was because I had two modules(mails and accounts) importing from each other, so a fix to that was to include the Inbox models in the account modules rather than creating a separate modules (mails).
I had to delete my mails modules and I ran into a couple of errors to resolve these errors I had to drop my database and then the accounts_engineer
table wasn't showing, using python manage.py migrate --run-syncdb
to migrate my changes helped resolved that issue.
Message
model
class Message(models.Model):
sender = models.ForeignKey("Engineer", null=True, on_delete=models.CASCADE, related_name='mail_sender')
receiver = models.ForeignKey("Engineer", null=True, on_delete=models.CASCADE, related_name="mail_receiver")
message = models.CharField(max_length=500)
updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["-created"]
def __str__(self):
return self.sender.username
The Message
model is pretty straight forward like every other model we have been creating
tim osahenru
~ 15 Dec 2022 2:10 a.m.
While creating the logic for the message function I ran into yet another error resulting from the line of this code
context['messages'] = sender_messages.union(received_messages)
error message is shown below
In Python the union()
method returns a set that contains all items from the original set, and all items from the specified set
However, had to change approach noticing that the error above is peculiar to using the sqlite3 database as discovered from this stackoverflow answer.
.distinct()
method can be used to perform this operation to achieve similar results. In Python .distinct()
is an expression/query clause used in SQL to remove duplicated results and return unique results based on the column we select.
The above line of code was later replaced with context['messages'] = (sender_messages | received_messages).distinct()
below is the full code to how the message function was created
tim osahenru
~ 15 Dec 2022 2:15 a.m.
(updated: 15 Dec 2022 7:59 a.m.)
class CreateMessage(CreateView):
model = Message
template_name = '../templates/create-message.html'
fields = ['message']
def get_context_data(self, **kwargs):
context = super(CreateMessage, self).get_context_data()
context['engineers'] = Engineer.objects.all().exclude(username=self.request.user)
pk = self.kwargs.get("pk")
context['engineer'] = Engineer.objects.get(pk=pk)
engineer = context['engineer']
sender_messages = Message.objects.filter(sender=self.request.user, receiver=engineer)
received_messages = Message.objects.filter(sender=engineer, receiver=self.request.user)
context['messages'] = (sender_messages | received_messages).distinct()
return context
def form_valid(self, form):
if form.is_valid():
message = form.save(commit=False)
message.sender = self.request.user
pk = self.kwargs.get("pk")
message.receiver = Engineer.objects.get(pk=pk)
form.save()
return redirect('message', pk=self.request.user.id)
return super(CreateMessage, self).form_valid(form)
Brief explanation of the above code block
tim osahenru
~ 15 Dec 2022 7:51 a.m.
(updated: 15 Dec 2022 7:57 a.m.)
context['engineers'] = Engineer.objects.all().exclude(username=self.request.user)
We don't want a logged in user messaging themselves so we get all the Engineers on our platform excluding the logged in user
context['engineer'] = Engineer.objects.get(pk=pk)
Get the id
of the Engineer
we want to message
sender_messages = Message.objects.filter(sender=self.request.user, receiver=engineer)
Filter the Message
objects where the requested user(logged in user) is the sender and the profile of the engineer clicked is the receiver all saved in sender_messages
variable
received_messages = Message.objects.filter(sender=engineer, receiver=self.request.user)
For an Engineer
to receive the message they have to be logged in (now the requested user) while the profile of the engineer they clicked on the sender
With the help of the .distict()
function we can pass both querysets (sender_messages
and received_messages
) in the variable messages
Processing a valid form
def form_valid(self, form):
if form.is_valid():
message = form.save(commit=False)
message.sender = self.request.user
pk = self.kwargs.get("pk")
message.receiver = Engineer.objects.get(pk=pk)
form.save()
return redirect('message', pk=self.request.user.id)
return super(CreateMessage, self).form_valid(form)
While creating the message object we want to pass both the sender
of the message and the receiver
of the message before the message is created or saved, the sender is the requested user and then we get the id
of the engineer receiving the message and once the message object has been created the user is redirected to their message inbox.
tim osahenru
~ 15 Dec 2022 7:40 p.m.
(updated: 15 Dec 2022 7:53 p.m.)
I've had quite a struggle trying to deploy my documentation on readthedocs, but after several trials finally figured it out.
added a .readthedocs.yaml
file to the root path of my docs
folder to provide the python version in which the project was built.
Trying to push my code to github I ran into these git errors
after spending sometime on stackoverflow I discovered it was a git error(don't know why yet) but with this line of codes
del .git\index
git reset
I was able to solve this error
For some reasons I ran into yet another error when I tried to .\make html
my .rst
file as shown below
I understand what the error says, although I don't know how the _static
folder got missing but I am happy I understood the error message and could solve it by creating a _static
folder inside the source
folder as shown below
tim osahenru
~ 15 Dec 2022 7:58 p.m.
Deploying on readthedocs
As I mentioned earlier I ran into several errors while trying to deploy my documentation, I had to include a .readthedocs.yaml
and a requirements.txt
to the root directory of the docs
folder and why I tried to build I ran into this error several times
A solution to this was to exclude whatever dependency that flags an error
VIOLLA!!!
I was able to build my documentation on readthedocs
tim osahenru
~ 15 Dec 2022 8:04 p.m.
Message template
Designed the messaging template as shown below
the project is made available at and the documentation to this project is also available here
Have fun interacting with this project Phew!!! fingers crossed
Nicholas Tollervey
~ 17 Dec 2022 2:25 p.m.
Hi Tim,
I've taken a look around the code on GitHub, the website online, and read the docs you managed to deploy online (well done!). I think I have enough evidence and observed enough activity and growth to be able to write up the assessment for your grade. This is generally a three step process:
- I write up my assessment.
- A third party checks it, points out corrections or advises on ways I can improve my feedback to you.
- Once the review is approved in step 2, you get your results.
I expect you should receive them sometime at the beginning of next week. 🚀
tim osahenru
~ 18 Dec 2022 4:06 p.m.
Well noted. Fingers crossed