class Project(NamedModel): """An Asana project in a workspace having a collection of tasks.""" layout_choices = ( ('board', _('board')), ('list', _('list')), )
archived = models.BooleanField(default=False) color = models.CharField(choices=COLOR_CHOICES, max_length=16, null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) current_status = models.ForeignKey( 'ProjectStatus', null=True, on_delete=models.SET_NULL, related_name='current_status') custom_field_settings = models.ManyToManyField( 'CustomField', through='CustomFieldSetting', related_name='custom_field_settings') due_date = models.DateField(null=True, blank=True) due_on = models.DateField(null=True, blank=True) followers = models.ManyToManyField('User', related_name='projects_following', blank=True) html_notes = models.TextField(null=True, blank=True) layout = models.CharField(choices=layout_choices, max_length=16) members = models.ManyToManyField('User', blank=True) modified_at = models.DateTimeField(auto_now=True) notes = models.TextField(null=True, blank=True) owner = models.ForeignKey( 'User', to_field='remote_id', related_name='projects_owned', null=True, on_delete=models.SET_NULL) public = models.BooleanField(default=False) resource_type = models.CharField(max_length=24, null=True, blank=True, default='project') start_on = models.DateField(null=True, blank=True) team = models.ForeignKey('Team', to_field='remote_id', null=True, on_delete=models.SET_NULL) workspace = models.ForeignKey('Workspace', to_field='remote_id', on_delete=models.CASCADE)
def asana_url(self, **kwargs): """Returns the absolute url for this project at Asana.""" return '{}{}/list'.format(ASANA_BASE_URL, self.remote_id)
class Task(Hearted, NamedModel): """An Asana task; something that needs doing.""" status_choices = ( ('inbox', _('inbox')), ('upcoming', _('upcoming')), ('later', _('later')), ) subtype_choices = ( ('default_task', _('default task')), ('section', _('section')), )
assignee = models.ForeignKey( 'User', to_field='remote_id', related_name='assigned_tasks', null=True, blank=True, on_delete=models.SET_NULL) assignee_status = models.CharField(choices=status_choices, max_length=16) completed = models.BooleanField(default=False) completed_at = models.DateTimeField(null=True, blank=True) custom_fields = models.TextField(null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) dependencies = models.ManyToManyField('self', symmetrical=False, related_name='dependents') due_at = models.DateTimeField(null=True, blank=True) due_on = models.DateField(null=True, blank=True) followers = models.ManyToManyField('User', related_name='tasks_following') html_notes = models.TextField(null=True, blank=True) modified_at = models.DateTimeField(auto_now=True) notes = models.TextField(null=True, blank=True) parent = models.ForeignKey( 'self', to_field='remote_id', null=True, blank=True, on_delete=models.SET_NULL) projects = models.ManyToManyField('Project') resource_subtype = models.CharField( choices=subtype_choices, max_length=24, default='default_task') resource_type = models.CharField(max_length=24, null=True, blank=True, default='task') start_on = models.DateField(null=True, blank=True) tags = models.ManyToManyField('Tag')
def _asana_project_url(self, project): return '{}{}/{}/list'.format(ASANA_BASE_URL, project.workspace.remote_id, self.remote_id)
def asana_url(self, **kwargs): """Returns the absolute url for this task at Asana.""" if 'project' in kwargs: return self._asana_project_url(kwargs['project']) projects = self.projects.all() if len(projects) == 1: project = projects[0] return self._asana_project_url(project) return super(Task, self).asana_url()
def delete_from_asana(self, *args, **kwargs): """Deletes this task from Asana and then deletes this model instance.""" client = client_connect() client.tasks.delete(self.remote_id) logger.debug('Deleted asana task %s', self.name) return self.delete(*args, **kwargs)
def due(self): return self.due_at or self.due_on due.admin_order_field = 'due_on'
def refresh_from_asana(self): """Updates this task from Asana.""" client = client_connect() task_dict = client.tasks.find_by_id(self.remote_id) if task_dict['assignee']: user = User.objects.get_or_create( remote_id=task_dict['assignee']['id'], defaults={'name': task_dict['assignee']['name']})[0] task_dict['assignee'] = user task_dict.pop('id') task_dict.pop('dependents', None) dependencies = task_dict.pop('dependencies', None) task_dict.pop('hearts', None) task_dict.pop('memberships') task_dict.pop('num_hearts', None) task_dict.pop('projects') task_dict.pop('workspace') followers_dict = task_dict.pop('followers') tags_dict = task_dict.pop('tags') for field, value in task_dict.items(): setattr(self, field, value) self.save() follower_ids = [follower['id'] for follower in followers_dict] followers = User.objects.filter(id__in=follower_ids) self.followers.set(followers) for tag_ in tags_dict: tag = Tag.objects.get_or_create( remote_id=tag_['id'], defaults={'name': tag_['name']})[0] self.tags.add(tag) if dependencies: self.dependencies.set([dep['id'] for dep in dependencies])
def sync_to_asana(self, fields=None): """Updates Asana to match values from this task.""" fields = fields or ['completed'] data = {} for field in fields: data[field] = getattr(self, field) client = client_connect() client.tasks.update(self.remote_id, data) logger.debug('Updated asana for task %s', self.name)
def add_comment(self, text): """Adds a comment in Asana for this task.""" client = client_connect() response = client.tasks.add_comment(self.remote_id, {'text': text}) logger.debug('Added comment for task %s: %s', self.name, text) return response
def get_custom_fields(self): """Returns custom_fields as a dict""" response = json.loads(self.custom_fields) custom_field_values = {} for custom_field in response: if custom_field['resource_subtype'] == 'enum': custom_field_values[custom_field['name']] = custom_field['enum_value']['name'] elif custom_field['resource_subtype'] == 'number': if custom_field.get('precision', 0): custom_field_values[custom_field['name']] = float(custom_field['number_value']) else: custom_field_values[custom_field['name']] = int(custom_field['number_value']) else: custom_field_values[custom_field['name']] = custom_field['text_value'] return custom_field_values
class Attachment(NamedModel): """A remote file.""" host_choices = ( ('asana', 'asana'), ) type_choices = ( ('image', 'image'), ('other', 'other'), ) created_at = models.DateTimeField(auto_now_add=True) download_url = models.URLField(max_length=1024) host = models.CharField(choices=host_choices, max_length=24) parent = models.ForeignKey('Task', to_field='remote_id', on_delete=models.CASCADE) permanent_url = models.URLField(max_length=1024) resource_type = models.CharField(max_length=24, null=True, blank=True, default='attachment') type = models.CharField(choices=type_choices, max_length=24, null=True, blank=True) view_url = models.URLField(max_length=1024)
def asana_url(self, **kwargs): return self.permanent_url
class ProjectDetail(SingleObjectMixin, ListView): queryset = Project.objects.all() template_name = 'dash1/project_detail.html'
def get(self, request, *args, **kwargs): self.object = self.get_object(queryset=Project.objects.all()) return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['Project'] = self.object context['task_list'] = Task.objects.filter(projects=8).order_by('-completed_at', 'name')[:5] context['attachment_list'] = Attachment.objects.all()[:5] return context{% block main %}<h1>{{ object }}</h1>{% for object in task_list.all %}<div class="row"> <div class="col-xl-4"> <div class="card-box project-box"> <div class="dropdown float-right"> <a href="#" class="dropdown-toggle card-drop arrow-none" data-toggle="dropdown" aria-expanded="false"> <h3 class="m-0 text-muted"><i class="mdi mdi-dots-horizontal"></i></h3> </a> <div class="dropdown-menu dropdown-menu-right" aria-labelledby="btnGroupDrop1"> <a class="dropdown-item" href="#">Edit</a> <a class="dropdown-item" href="#">Delete</a> <a class="dropdown-item" href="#">Add Members</a> <a class="dropdown-item" href="#">Add Due Date</a> </div> </div> <p class="text-muted text-uppercase mb-0 font-13">DUE: {{ object.due_on }}</p> <h4 class="mt-0 mb-3"><a href="" class="text-dark">{{ object.name }}</a></h4> <p class="text-muted font-13">{{ object.notes }}<a href="#" class="font-600 text-muted">view more</a> </p>
<ul class="list-inline"> {% for object in attachment_list.all %} <li class="list-inline-item"> <img src="{{ object.permanent_url }}" height="30px"> </li> {% endfor %} </ul>
<label class="">Task completed: <span class="text-custom">{{ object.completed_at }}</span></label> </div><!-- /.progress .no-rounded --> </div> </div></div> {% empty %} <p class="lead"> No Tasks Loaded </p> {% endfor %}
{% endblock %}