-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add projects #1
add projects #1
Conversation
|
||
|
||
class ProjectAdmin(admin.ModelAdmin): | ||
list_display = ['name', 'status', 'customer', 'get_members_count'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
members
may be a better name here
ssm/ssm/projects/admin.py
Outdated
list_display = ['name', 'status', 'customer', 'get_members_count'] | ||
search_fields = ['name'] | ||
fieldsets = [ | ||
(None, {'fields': ['name', 'description', 'specification', 'status', 'customer', 'estimation_in_man_hours']}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One Details
tab for all information?
inlines = [MembershipInline] | ||
list_filter = ['customer'] | ||
|
||
def get_members_count(self, obj): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
members
may be a better name here
ssm/ssm/projects/admin.py
Outdated
list_filter = ['customer'] | ||
|
||
def get_members_count(self, obj): | ||
return len(obj.members.filter(membershipmodel__date_left=None)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please always use count()
instead of len()
on QuerySets if all you want to do is determine the number of records in the set. It's much more efficient to handle a count at the database level, using SQL's SELECT COUNT(*), and Django provides a count() method for precisely this reason.
ssm/ssm/projects/apps.py
Outdated
|
||
|
||
class ProjectsConfig(AppConfig): | ||
name = 'projects' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not needed, please remove
name='hours_per_day', | ||
field=models.IntegerField(default=8, verbose_name='Hours per day'), | ||
), | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please try to use less amount of migrations before making your pull request, in your case when you do one new app support from scratch you can always remove all migrations for project at the end and recreate them to have clear picture for reviewer without all these changes history. Of course you shouldn't use such deletion migration practice in all other cases, but for current workflow it's quite a good one
|
||
|
||
class Project(BaseModel): | ||
name = models.CharField('Name', max_length=32, unique=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't guarantee "true" uniqueness here as "Project" and "pRoject" values will not be recognized as one value, also you don't handle spaces at the beginning or at the end of name, so "Project" and "Project " also will be different names, you will need to design uniqueness rule here, for example use [title()](https://docs.python.org/3/library/stdtypes.html#str.title)
and [strip()](https://docs.python.org/3/library/stdtypes.html#str.strip)
combination () for name
value, the same problem was for Skill
model where save(...)
method was modified to always force lower()
and strip()
:
def save(self, *args, **kwargs):
self.name = self.name.lower().strip()
super().save(*args, **kwargs)
ssm/ssm/projects/models.py
Outdated
class Project(BaseModel): | ||
name = models.CharField('Name', max_length=32, unique=True) | ||
description = models.TextField('Description', null=True, blank=True) | ||
specification = models.FileField('Specification', null=True, blank=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should store specification
files in location which will be mapped to docker volume, otherwise you will loose all data after container deletion/recreation, you should be persistent here, also you will have file name collision here if different projects will have the same name for specification, for example specification.docs
, so you need to modify model field with upload_to
attribute, for example, models.FileField(upload_to='/var/ssm/data/project_{ID}/')
, then create /var/ssm/data/
in Dockerfile with RUN mkdir -p /var/ssm/data/
command and map this location to docker volume in docker-compose-local.yml
, something like this
volumes:
ssm-data:
driver: local
......
ssm:
build:
context: ../
volumes:
- ssm_data:/var/ssm/data/
name = models.CharField('Name', max_length=32, unique=True) | ||
description = models.TextField('Description', null=True, blank=True) | ||
specification = models.FileField('Specification', null=True, blank=True) | ||
customer = models.ForeignKey(User, on_delete=models.DO_NOTHING, verbose_name='Customer', null=True, blank=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need a separate field for customer
? Can he be included in members
like all other team with it's own role? What are props and cons of this design approach?
ssm/ssm/projects/models.py
Outdated
description = models.TextField('Description', null=True, blank=True) | ||
specification = models.FileField('Specification', null=True, blank=True) | ||
customer = models.ForeignKey(User, on_delete=models.DO_NOTHING, verbose_name='Customer', null=True, blank=True) | ||
start_date = models.DateField('Start date', null=True, blank=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use title()
convention for explanation text, so Start date
should be changed to Start Date
ssm/ssm/projects/models.py
Outdated
specification = models.FileField('Specification', null=True, blank=True) | ||
customer = models.ForeignKey(User, on_delete=models.DO_NOTHING, verbose_name='Customer', null=True, blank=True) | ||
start_date = models.DateField('Start date', null=True, blank=True) | ||
end_date = models.DateField('End date', null=True, blank=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be changed to End Date
|
||
class Meta: | ||
app_label = 'projects' | ||
ordering = ['-modified'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please stay consistent here with ordering and Meta attributes, should be:
class Meta:
app_label = 'projects'
verbose_name_plural = 'Projects'
ordering = ['-modified']
ssm/ssm/projects/models.py
Outdated
user = models.ForeignKey(User, on_delete=models.CASCADE) | ||
project = models.ForeignKey(Project, on_delete=models.CASCADE) | ||
role = models.CharField('Role in project', choices=ROLES, max_length=64, blank=False, null=False, | ||
default=ROLES.developer) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's try to fit this line in one row, no need for detailed Role in project
explanation, simple Role
is already enough to clarify for what we use this field, and this change allow us to feet 119 limit which is a standard for us.
Also please stay consistent with order of attributes, it should be:
role = models.CharField('Role', max_length=64, blank=False, null=False, choices=ROLES, default=ROLES.developer)
ssm/ssm/projects/models.py
Outdated
role = models.CharField('Role in project', choices=ROLES, max_length=64, blank=False, null=False, | ||
default=ROLES.developer) | ||
hours_per_day = models.IntegerField('Hours per day', blank=False, null=False, default=8) | ||
date_joined = models.DateField('Date joined') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's also stay consistent here in names, should be joined_date = models.DateField('Joined Date')
ssm/ssm/projects/models.py
Outdated
default=ROLES.developer) | ||
hours_per_day = models.IntegerField('Hours per day', blank=False, null=False, default=8) | ||
date_joined = models.DateField('Date joined') | ||
date_left = models.DateField('Date of leaving', blank=True, null=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be left_date = models.DateField('Left Date', null=True, blank=True)
class Meta: | ||
verbose_name_plural = 'Project members' | ||
unique_together = ('user', 'project', 'role') | ||
ordering = ['user'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please stay consistent here with ordering and Meta attributes like in other places:
class Meta:
app_label = 'projects'
verbose_name_plural = 'Project members'
unique_together = ('user', 'project', 'role')
ordering = ['user']
ssm/ssm/projects/permissions.py
Outdated
|
||
class IsProjectMemberOrStaff(permissions.BasePermission): | ||
def has_object_permission(self, request, view, obj): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Empty line, please remove
ssm/ssm/projects/permissions.py
Outdated
|
||
class ReadOnlyOrStaff(permissions.BasePermission): | ||
def has_permission(self, request, view): | ||
return True if request.user.is_staff else request.method in permissions.SAFE_METHODS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks familiar, can we make it DRY?
from rest_framework.serializers import ModelSerializer | ||
from drf_dynamic_fields import DynamicFieldsMixin | ||
from rest_framework import serializers | ||
from drf_dynamic_fields import DynamicFieldsMixin |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove duplicate from drf_dynamic_fields import DynamicFieldsMixin
import and also stay consistent with selected approach, either import the whole from rest_framework import serializers
and use it class MembershipSerializer(serializers.ModelSerializer):
or import all needed things at once from rest_framework.serializers import ModelSerializer, ReadOnlyField
ssm/ssm/projects/serializers.py
Outdated
|
||
class ProjectSerializer(DynamicFieldsMixin, ModelSerializer): | ||
members = MembershipSerializer(source='membershipmodel_set', many=True, read_only=True) | ||
customer = CustomerSerializer(many=False, read_only=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
many=False
already false by default, in most cases no need to specify default values
@@ -0,0 +1,3 @@ | |||
from django.test import TestCase | |||
|
|||
# Create your tests here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove, it's not needed for now
from rest_framework.filters import OrderingFilter | ||
from rest_framework.permissions import IsAuthenticated | ||
from django_filters.rest_framework import DjangoFilterBackend | ||
from rest_framework.filters import OrderingFilter, SearchFilter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please change the order of imports, imports from one package should go one by one:
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.viewsets import ModelViewSet
from rest_framework.filters import OrderingFilter
from rest_framework.permissions import IsAuthenticated
from rest_framework.filters import OrderingFilter, SearchFilter
ssm/ssm/projects/views.py
Outdated
filter_fields = ['customer__full_name'] | ||
ordering = ['name'] | ||
|
||
def get_queryset(self, *args, **kwargs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove custom get_queryset
and use filter instead
ssm/ssm/users/views.py
Outdated
@@ -23,7 +23,7 @@ class SSMTokenObtainPairView(CustomTokenObtainPairView): | |||
class UserViewSet(ModelViewSet): | |||
queryset = User.objects.all() | |||
serializer_class = UserSerializer | |||
permission_classes = [ReadOnlyOrStaff, IsCurrentUserOrStaff] | |||
permission_classes = [ReadOnlyOrStaff, IsCurrentUserOrStaff, IsAuthenticated] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IsAuthenticated
should go first, consistency everywhere
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made initial review and added comments for all places which should be changed/discussed. Also note that I pushed some updates in original staff
project which you may need to merge in order to not have conflicts later
I finally fixed code after code review) |
No description provided.