Skip to content
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

Merged
merged 4 commits into from
Apr 19, 2019
Merged

add projects #1

merged 4 commits into from
Apr 19, 2019

Conversation

pitskod
Copy link
Contributor

@pitskod pitskod commented Feb 17, 2019

No description provided.



class ProjectAdmin(admin.ModelAdmin):
list_display = ['name', 'status', 'customer', 'get_members_count']
Copy link
Owner

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

list_display = ['name', 'status', 'customer', 'get_members_count']
search_fields = ['name']
fieldsets = [
(None, {'fields': ['name', 'description', 'specification', 'status', 'customer', 'estimation_in_man_hours']}),
Copy link
Owner

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):
Copy link
Owner

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

list_filter = ['customer']

def get_members_count(self, obj):
return len(obj.members.filter(membershipmodel__date_left=None))
Copy link
Owner

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.



class ProjectsConfig(AppConfig):
name = 'projects'
Copy link
Owner

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'),
),
]
Copy link
Owner

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)
Copy link
Owner

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)

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)
Copy link
Owner

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)
Copy link
Owner

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?

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)
Copy link
Owner

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

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)
Copy link
Owner

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']
Copy link
Owner

@twistedFantasy twistedFantasy Feb 19, 2019

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']

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)
Copy link
Owner

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)

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')
Copy link
Owner

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')

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)
Copy link
Owner

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']
Copy link
Owner

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']


class IsProjectMemberOrStaff(permissions.BasePermission):
def has_object_permission(self, request, view, obj):

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty line, please remove


class ReadOnlyOrStaff(permissions.BasePermission):
def has_permission(self, request, view):
return True if request.user.is_staff else request.method in permissions.SAFE_METHODS
Copy link
Owner

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
Copy link
Owner

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


class ProjectSerializer(DynamicFieldsMixin, ModelSerializer):
members = MembershipSerializer(source='membershipmodel_set', many=True, read_only=True)
customer = CustomerSerializer(many=False, read_only=True)
Copy link
Owner

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.
Copy link
Owner

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
Copy link
Owner

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

filter_fields = ['customer__full_name']
ordering = ['name']

def get_queryset(self, *args, **kwargs):
Copy link
Owner

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

@@ -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]
Copy link
Owner

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

Copy link
Owner

@twistedFantasy twistedFantasy left a 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

@pitskod
Copy link
Contributor Author

pitskod commented Mar 10, 2019

I finally fixed code after code review)

@twistedFantasy twistedFantasy merged commit fa0d3a8 into twistedFantasy:master Apr 19, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants