From 847272ce32ef7ebe0ca64f937bee9e4876d0245b Mon Sep 17 00:00:00 2001 From: nikhila05 Date: Mon, 28 Jan 2019 20:55:58 +0530 Subject: [PATCH] Changes added documents functionality, displaying preview icon for documents, downloading documents files (#152) * removed migartion files, created single migration file, updating closed on, start & end date for emails, events, cases, opportunities, * added documents functionality, displaying preview icon for documents, downloading documents --- README.rst | 17 ---- common/forms.py | 9 +- common/migrations/0003_document.py | 26 +++++ common/models.py | 148 +++++++++++++++++++++++++++++ common/templates/doc_create.html | 72 ++++++++++++++ common/templates/doc_detail.html | 57 +++++++++++ common/templates/doc_list.html | 114 ++++++++++++++++++++++ common/urls.py | 13 ++- common/views.py | 129 ++++++++++++++++++++++++- templates/base.html | 1 + 10 files changed, 564 insertions(+), 22 deletions(-) create mode 100644 common/migrations/0003_document.py create mode 100644 common/templates/doc_create.html create mode 100644 common/templates/doc_detail.html create mode 100644 common/templates/doc_list.html diff --git a/README.rst b/README.rst index 217fbde5..a4fe812b 100644 --- a/README.rst +++ b/README.rst @@ -63,23 +63,6 @@ This project contains the following modules. * Planner -Installation - Requirements -=========================== - - -Ubuntu 64bit - 16.04 --------------------- -$ sudo apt-get update && apt-get upgrade -y - -$ sudo apt-get install -y curl wget libpq-dev python3-dev gem ruby ruby-dev build-essential libssl-dev libffi-dev python-dev python-virtualenv python-pip git redis-server libtiff5-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev libharfbuzz-dev libfribidi-dev tcl8.6-dev tk8.6-dev python-tk - -$ sudo gem install sass - - -If you cloned the package from git use virtualenv to install requirements:: - - pip install -r requirements.txt - Try === Demo Available `here`_. diff --git a/common/forms.py b/common/forms.py index fce56111..1e8577e2 100644 --- a/common/forms.py +++ b/common/forms.py @@ -1,7 +1,7 @@ from django import forms from django.contrib.auth import authenticate from django.contrib.auth.forms import PasswordResetForm -from common.models import Address, User +from common.models import Address, User, Document class BillingAddressForm(forms.ModelForm): @@ -143,3 +143,10 @@ class PasswordResetEmailForm(PasswordResetForm): if not User.objects.filter(email__iexact=email, is_active=True).exists(): raise forms.ValidationError("User doesn't exist with this Email") return email + + +class DocumentForm(forms.ModelForm): + + class Meta: + model = Document + fields = ['title', 'document_file'] \ No newline at end of file diff --git a/common/migrations/0003_document.py b/common/migrations/0003_document.py new file mode 100644 index 00000000..2861c5f8 --- /dev/null +++ b/common/migrations/0003_document.py @@ -0,0 +1,26 @@ +# Generated by Django 2.1.5 on 2019-01-28 07:28 + +import common.models +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0002_auto_20190128_1237'), + ] + + operations = [ + migrations.CreateModel( + name='Document', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(blank=True, max_length=1000, null=True)), + ('document_file', models.FileField(max_length=5000, upload_to=common.models.document_path)), + ('created_on', models.DateTimeField(auto_now_add=True)), + ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='document_uploaded', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/common/models.py b/common/models.py index 68d98958..05e7b1ef 100644 --- a/common/models.py +++ b/common/models.py @@ -1,4 +1,6 @@ from django.db import models +from datetime import datetime +from django.template.defaultfilters import slugify from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, UserManager @@ -125,3 +127,149 @@ class Attachments(models.Model): account = models.ForeignKey('accounts.Account', null=True, blank=True, related_name='account_attachment', on_delete=models.CASCADE) contact = models.ForeignKey('contacts.Contact', on_delete=models.CASCADE, related_name='contact_attachment', blank=True, null=True) opportunity = models.ForeignKey('opportunity.Opportunity',blank=True,null=True,on_delete=models.CASCADE,related_name='opportunity_attachment') + + +def document_path(self, filename): + hash_ = int(time.time()) + return "%s/%s/%s" % ("docs", hash_, filename) + +class Document(models.Model): + title = models.CharField(max_length=1000, blank=True, null=True) + document_file = models.FileField(upload_to=document_path, max_length=5000) + created_by = models.ForeignKey(User, related_name='document_uploaded', on_delete=models.CASCADE) + created_on = models.DateTimeField(auto_now_add=True) + + def is_document_file_image(self, ext): + image_ext_list = ['bmp', 'dds', 'gif', 'jpg', 'jpeg', 'png', 'psd', 'pspimage', + 'tga', 'thm', 'tif', 'tiff', 'yuv'] + return ext.lower() in image_ext_list + + def is_document_file_audio(self, ext): + audio_ext_list = ['aif', 'iff', 'm3u', 'm4a', 'mid', 'mp3', + 'mpa', 'wav', 'wma'] + return ext.lower() in audio_ext_list + + def is_document_file_video(self, ext): + video_ext_list = ['3g2', '3gp', 'asf', 'avi', 'flv', 'm4v', 'mov', + 'mp4', 'mpg', 'rm', 'srt', 'swf', 'vob', 'wmv'] + return ext.lower() in video_ext_list + + def is_document_file_pdf(self, ext): + pdf_ext_list = ['indd', 'pct', 'pdf'] + return ext.lower() in pdf_ext_list + + def is_document_file_code(self, ext): + code_ext_list = ['aspx', 'json', 'jsp', 'do', 'htm', 'html', 'ser', + 'php', 'jad', 'cfm', 'xml', 'js', 'pod', 'asp', + 'atomsvc', 'rdf', 'pou', 'jsf', 'abs', 'pl', 'asm', + 'srz', 'luac', 'cod', 'lib', 'arxml', 'bas', 'ejs', + 'fs', 'hbs', 's', 'ss', 'cms', 'pyc', 'vcxproj', + 'jse', 'smali', 'xla', 'lxk', 'pdb', 'src', 'cs', + 'ipb', 'ave', 'mst', 'vls', 'rcc', 'sax', 'scr', + 'dtd', 'axd', 'mrl', 'xsl', 'ino', 'spr', 'xsd', + 'cgi', 'isa', 'ws', 'rss', 'dvb', 'nupkg', 'xlm', + 'v4e', 'rss', 'prg', 'form', 'bat', 'mrc', 'asi', + 'jdp', 'fmb', 'graphml', 'gcode', 'aia', 'py', 'atp', + 'mzp', 'o', 'scs', 'mm', 'cpp', 'java', 'gypi', 'idb', + 'txml', 'c', 'vip', 'tra', 'rc', 'action', 'vlx', + 'asta', 'pyo', 'lua', 'gml', 'prl', 'rfs', 'cpb', + 'sh', 'rbf', 'gp', 'phtml', 'bp', 'scb', 'sln', 'vbp', + 'wbf', 'bdt', 'mac', 'rpy', 'eaf', 'mc', 'mwp', 'gnt', + 'h', 'swift', 'e', 'styl', 'cxx', 'as', 'liquid', + 'dep', 'fas', 'vbs', 'aps', 'vbe', 'lss', 'cmake', + 'resx', 'csb', 'dpk', 'pdml', 'txx', 'dbg', 'jsa', + 'sxs', 'sasf', 'pm', 'csx', 'r', 'wml', 'au3', 'stm', + 'cls', 'cc', 'ins', 'jsc', 'dwp', 'rpg', 'arb', 'bml', + 'inc', 'eld', 'sct', 'sm', 'wbt', 'csproj', 'tcz', + 'html5', 'gbl', 'cmd', 'dlg', 'tpl', 'rbt', 'xcp', + 'tpm', 'qry', 'mfa', 'ptx', 'lsp', 'pag', 'ebc', + 'php3', 'cob', 'csc', 'pyt', 'dwt', 'rb', 'wsdl', + 'lap', 'textile', 'sfx', 'x', 'a5r', 'dbp', 'pmp', + 'ipr', 'fwx', 'pbl', 'vbw', 'phl', 'cbl', 'pas', + 'mom', 'dbmdl', 'lol', 'wdl', 'ppam', 'plx', 'vb', + 'cgx', 'lst', 'lmp', 'vd', 'bcp', 'thtml', 'scpt', + 'isu', 'mrd', 'perl', 'dtx', 'f', 'wpk', 'ipf', 'ptl', + 'luca', 'hx', 'uvproj', 'qvs', 'vba', 'xjb', + 'appxupload', 'ti', 'svn-base', 'bsc', 'mak', + 'vcproj', 'dsd', 'ksh', 'pyw', 'bxml', 'mo', 'irc', + 'gcl', 'dbml', 'mlv', 'wsf', 'tcl', 'dqy', 'ssi', + 'pbxproj', 'bal', 'trt', 'sal', 'hkp', 'vbi', 'dob', + 'htc', 'p', 'ats', 'seam', 'loc', 'pli', 'rptproj', + 'pxml', 'pkb', 'dpr', 'scss', 'dsb', 'bb', 'vbproj', + 'ash', 'rml', 'nbk', 'nvi', 'lmv', 'mw', 'jl', 'dso', + 'cba', 'jks', 'ary', 'run', 'vps', 'clm', 'brml', + 'msha', 'mdp', 'tmh', 'rdf', 'jsx', 'sdl', 'ptxml', + 'fxl', 'wmw', 'dcr', 'bcc', 'cbp', 'bmo', 'bsv', + 'less', 'gss', 'ctl', 'rpyc', 'ascx', 'odc', 'wiki', + 'obr', 'l', 'axs', 'bpr', 'ppa', 'rpo', 'sqlproj', + 'smm', 'dsr', 'arq', 'din', 'jml', 'jsonp', 'ml', + 'rc2', 'myapp', 'cla', 'xme', 'obj', 'jsdtscope', + 'gyp', 'datasource', 'cp', 'rh', 'lpx', 'a2w', 'ctp', + 'ulp', 'nt', 'script', 'bxl', 'gs', 'xslt', 'mg', + 'pch', 'mhl', 'zpd', 'psm1', 'asz', 'm', 'jacl', + 'pym', 'rws', 'acu', 'ssq', 'wxs', 'coffee', 'ncb', + 'akt', 'pyx', 'zero', 'hs', 'mkb', 'tru', 'xul', + 'mfl', 'sca', 'sbr', 'master', 'opv', 'matlab', + 'sami', 'agc', 'slim', 'tea', 'pbl', 'm51', 'mec', + 'asc', 'gch', 'enml', 'ino', 'kst', 'jade', 'dfb', + 'ips', 'rgs', 'vbx', 'cspkg', 'ncx', 'brs', 'wfs', + 'ifp', 'nse', 'xtx', 'j', 'cx', 'ps1', 'nas', 'mk', + 'ccs', 'vrp', 'lnp', 'cml', 'c#', 'idl', 'exp', 'apb', + 'nsi', 'asmx', 'tdo', 'pjt', 'fdt', 's5d', 'mvba', + 'mf', 'odl', 'bzs', 'jardesc', 'tgml', 'moc', 'wxi', + 'cpz', 'fsx', 'jav', 'ocb', 'agi', 'tec', 'txl', + 'amw', 'mscr', 'dfd', 'dpd', 'pun', 'f95', 'vdproj', + 'xsc', 'diff', 'wxl', 'dgml', 'airi', 'kmt', 'ksc', + 'io', 'rbw', 'sas', 'vcp', 'resources', 'param', + 'cg', 'hlsl', 'vssscc', 'bgm', 'xn', 'targets', 'sl', + 'gsc', 'qs', 'owl', 'devpak', 'phps', 'hdf', 'pri', + 'nbin', 'xaml', 's4e', 'scm', 'tk', 'poc', 'uix', + 'clw', 'factorypath', 's43', 'awd', 'htr', 'php2', + 'classpath', 'pickle', 'rob', 'msil', 'ebx', 'tsq', + 'lml', 'f90', 'lds', 'vup', 'pbi', 'swt', 'vap', 'ig', + 'pdo', 'frt', 'fcg', 'c++', 'xcl', 'dfn', 'aar', + 'for', 're', 'twig', 'ebm', 'dhtml', 'hc', 'pro', + 'ahk', 'rule', 'bsh', 'jcs', 'zrx', 'wsdd', 'csp', + 'drc', 'appxsym'] + return ext.lower() in code_ext_list + + def is_document_file_text(self, ext): + text_ext_list = ['doc', 'docx', 'log', 'msg', 'odt', 'pages', 'rtf', + 'tex', 'txt', 'wpd', 'wps'] + return ext.lower() in text_ext_list + + def is_document_file_sheet(self, ext): + sheet_ext_list = ['csv', 'xls', 'xlsx', + 'xlsm', 'xlsb', 'xltx', 'xltm', 'xlt'] + return ext.lower() in sheet_ext_list + + def is_document_file_zip(self, ext): + ext_list = ['zip', '7Z', 'gz', 'rar', 'ZIPX', 'ACE', 'tar', ] + return ext.lower() in ext_list + + def file_type(self): + name_ext_list = self.document_file.url.split(".") + if (len(name_ext_list) > 1): + ext = name_ext_list[int(len(name_ext_list) - 1)] + if self.is_document_file_audio(ext): + return ("audio", "fa fa-file-audio") + elif self.is_document_file_video(ext): + return ("video", "fa fa-file-video") + elif self.is_document_file_image(ext): + return ("image", "fa fa-file-image") + elif self.is_document_file_pdf(ext): + return ("pdf", "fa fa-file-pdf") + elif self.is_document_file_code(ext): + return ("code", "fa fa-file-code") + elif self.is_document_file_text(ext): + return ("text", "fa fa-file-alt") + elif self.is_document_file_sheet(ext): + return ("sheet", "fa fa-file-excel") + elif self.is_document_file_zip(ext): + return ("zip", "fa fa-file-archive") + return ("file", "fa fa-file") + return ("file", "fa fa-file") + + + def __str__(self): + return self.title \ No newline at end of file diff --git a/common/templates/doc_create.html b/common/templates/doc_create.html new file mode 100644 index 00000000..d107b68e --- /dev/null +++ b/common/templates/doc_create.html @@ -0,0 +1,72 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% block breadcrumb %} + + +{% endblock breadcrumb %} +{% block content %} +
+
+
+
+
+
+ {% if doc_obj %}EDIT{% else %}CREATE{% endif %} DOCUMENT +
+
+
+
+
+ + +
+ {{ errors.title }} +
+
+
+ + +
+ {% if doc_obj %}{{doc_obj.document_file }}{% endif %} + {{ errors.document_file }} +
+
+
+
+ + Cancel +
+
+
+
+
+
+{% endblock content %} +{% block js_block %} + + +{% endblock js_block %} diff --git a/common/templates/doc_detail.html b/common/templates/doc_detail.html new file mode 100644 index 00000000..71459860 --- /dev/null +++ b/common/templates/doc_detail.html @@ -0,0 +1,57 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% load thumbnail %} +{% block content%} + + +
+
+
+
+
+
+
+ Document +
+ Edit +
+
+
+ {% if doc_obj.title %} +
+
+ +
{{ doc_obj.title }}
+
+
+ {% endif %} +
+
+ +
{{ doc_obj.created_by }}
+
+
+
+
+ +
{{ doc_obj.created_on }}
+
+
+
+
+
+ {% if doc_obj.document_file %} + + + Download + {% endif %} +
+
+
+
+
+
+
+
+ +{% endblock %} diff --git a/common/templates/doc_list.html b/common/templates/doc_list.html new file mode 100644 index 00000000..aa2084eb --- /dev/null +++ b/common/templates/doc_list.html @@ -0,0 +1,114 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% load paginate %} +{% block content %} + +
+ +
+
+
+
+
+ +
+
+
+ + +
+
+
+
+
Filters
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ List of Documents + {% if show_pageitems %} {% show_pageitems %}{% else %}Total Documents {{ documents|length }}{% endif %} +
+
+ + + {% if documents|length > 0 %} + + + + + + + + {% endif %} + + + {% if per_page %} + {% paginate per_page documents %} + {% else %} + {% paginate 10 documents %} + {% endif %} + {% for document in documents %} + + + + + + + {% endfor %} + +
S.NoDocument NameCreated_byActions
{{ forloop.counter }}{{ document.title }}{{ document.created_by }} + Edit + Delete +
+
+ {% ifequal documents|length 0 %} +
No Documents Found
+ {% endifequal %} +
+ {% show_pages %} +
+
+
+
+
+
+ +{% endblock %} +{% block js_block %} + +{% endblock js_block %} \ No newline at end of file diff --git a/common/urls.py b/common/urls.py index b6379a6b..a7954ffe 100644 --- a/common/urls.py +++ b/common/urls.py @@ -3,7 +3,9 @@ from django.contrib.auth import views as auth_views from django.urls import path, re_path from common.views import ( HomeView, LoginView, ForgotPasswordView, LogoutView, ChangePasswordView, ProfileView, - UsersListView, CreateUserView, UpdateUserView, UserDetailView, UserDeleteView, PasswordResetView) + UsersListView, CreateUserView, UpdateUserView, UserDetailView, UserDeleteView, PasswordResetView, + DocumentListView, DocumentCreateView, UpdateDocumentView, DocumentDetailView, DocumentDeleteView, + download_document) from django.conf.urls.static import static from django.conf import settings @@ -33,5 +35,14 @@ urlpatterns = [ 'reset///', auth_views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'), path('reset/done/', auth_views.PasswordResetCompleteView.as_view(), name='password_reset_complete'), + # Document + path('documents/list/', DocumentListView.as_view(), name='doc_list'), + path('documents/create/', DocumentCreateView.as_view(), name='create_doc'), + path('documents//edit/', UpdateDocumentView.as_view(), name="edit_doc"), + path('documents//view/', DocumentDetailView.as_view(), name='view_doc'), + path('documents//delete/', DocumentDeleteView.as_view(), name='remove_doc'), + + # download + path('documents//download/', download_document, name='download_document'), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/common/views.py b/common/views.py index 5d7f34b3..108885c0 100644 --- a/common/views.py +++ b/common/views.py @@ -1,12 +1,13 @@ +import os from django.contrib.auth import logout, authenticate, login from django.contrib.auth.hashers import check_password from django.contrib.auth.mixins import LoginRequiredMixin, AccessMixin -from django.http import HttpResponseRedirect, JsonResponse +from django.http import HttpResponseRedirect, JsonResponse, HttpResponse, Http404 from django.shortcuts import render, redirect, get_object_or_404 from django.views.generic import ( CreateView, UpdateView, DetailView, TemplateView, View, DeleteView) -from common.models import User -from common.forms import UserForm, LoginForm, ChangePasswordForm, PasswordResetEmailForm +from common.models import User, Document +from common.forms import UserForm, LoginForm, ChangePasswordForm, PasswordResetEmailForm,DocumentForm from django.contrib.auth.views import PasswordResetView from django.urls import reverse_lazy from django.conf import settings @@ -237,3 +238,125 @@ class PasswordResetView(PasswordResetView): template_name = 'registration/password_reset_form.html' form_class = PasswordResetEmailForm email_template_name = 'registration/password_reset_email.html' + + + +class DocumentCreateView(AdminRequiredMixin, CreateView): + model = Document + form_class = DocumentForm + template_name = "doc_create.html" + + def form_valid(self, form): + print('heeee') + doc = form.save(commit=False) + doc.created_by = self.request.user + doc.save() + print('doc saved') + if self.request.is_ajax(): + data = {'success_url': reverse_lazy('common:doc_list'), 'error': False} + return JsonResponse(data) + return super(DocumentCreateView, self).form_valid(form) + + + def form_invalid(self, form): + response = super(DocumentCreateView, self).form_invalid(form) + if self.request.is_ajax(): + print(form.errors, 'qeeee') + return JsonResponse({'error': True, 'errors': form.errors}) + return response + + def get_context_data(self, **kwargs): + context = super(DocumentCreateView, self).get_context_data(**kwargs) + context["doc_form"] = context["form"] + if "errors" in kwargs: + context["errors"] = kwargs["errors"] + return context + + + +class DocumentListView(AdminRequiredMixin, TemplateView): + model = Document + context_object_name = "documents" + template_name = "doc_list.html" + + def get_queryset(self): + queryset = self.model.objects.all() + request_post = self.request.POST + if request_post: + if request_post.get('doc_name'): + queryset = queryset.filter(title__icontains=request_post.get('doc_name')) + return queryset + + def get_context_data(self, **kwargs): + context = super(DocumentListView, self).get_context_data(**kwargs) + context["documents"] = self.get_queryset() + context["per_page"] = self.request.POST.get('per_page') + return context + + def post(self, request, *args, **kwargs): + context = self.get_context_data(**kwargs) + return self.render_to_response(context) + + +class DocumentDeleteView(AdminRequiredMixin, DeleteView): + model = Document + + def get(self, request, *args, **kwargs): + self.object = self.get_object() + self.object.delete() + return redirect("common:doc_list") + + +class UpdateDocumentView(LoginRequiredMixin, UpdateView): + model = Document + form_class = DocumentForm + template_name = "doc_create.html" + + def form_valid(self, form): + doc = form.save(commit=False) + doc.save() + if self.request.is_ajax(): + data = {'success_url': reverse_lazy('common:doc_list'), 'error': False} + return JsonResponse(data) + return super(UpdateDocumentView, self).form_valid(form) + + + def form_invalid(self, form): + response = super(UpdateDocumentView, self).form_invalid(form) + if self.request.is_ajax(): + return JsonResponse({'error': True, 'errors': form.errors}) + return response + + def get_context_data(self, **kwargs): + context = super(UpdateDocumentView, self).get_context_data(**kwargs) + context["doc_obj"] = self.object + context["doc_form"] = context["form"] + if "errors" in kwargs: + context["errors"] = kwargs["errors"] + return context + + +class DocumentDetailView(AdminRequiredMixin, DetailView): + model = Document + template_name = "doc_detail.html" + + def get_context_data(self, **kwargs): + context = super(DocumentDetailView, self).get_context_data(**kwargs) + documents = Document.objects.all() + context.update({ + "file_type_code": self.object.file_type()[1], + "doc_obj": self.object, + }) + return context + + +def download_document(request, pk): + doc_obj = Document.objects.filter(id=pk).last() + path = doc_obj.document_file.path + file_path = os.path.join(settings.MEDIA_ROOT, path) + if os.path.exists(file_path): + with open(file_path, 'rb') as fh: + response = HttpResponse(fh.read(), content_type="application/vnd.ms-excel") + response['Content-Disposition'] = 'inline; filename=' + os.path.basename(file_path) + return response + raise Http404 \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 23f1eba2..0776c374 100644 --- a/templates/base.html +++ b/templates/base.html @@ -37,6 +37,7 @@ +