diff --git a/Procfile b/Procfile deleted file mode 100644 index c6ee60d4..00000000 --- a/Procfile +++ /dev/null @@ -1 +0,0 @@ -web: sh -c 'gunicorn crm.wsgi && pip install -r requirements.txt' \ No newline at end of file diff --git a/README.rst b/README.rst index 396ccee7..fae6498d 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,8 @@ Django-CRM ========== -Django-CRM provides a Customer relationship management dashboard where you can manage customers at sales of the organization. It Provides to manage leads information and its activity, track issues from leads, manage contacts to send emails of their progress. +Django CRM is opensourse CRM developed on django framework. It has all the basic features of CRM to start with. We welcome code contributions and feature requests via github. + .. image:: https://travis-ci.org/MicroPyramid/Django-CRM.svg?branch=master :target: https://travis-ci.org/MicroPyramid/Django-CRM @@ -31,11 +32,19 @@ This project contains the following modules. * Opportunity * Planner -Installation -============ -dependency for debian and ubuntu: - apt install ruby-sass +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:: @@ -54,4 +63,6 @@ Credentials to CRM Dashboard: We welcome your feedback and support, raise github ticket if you want to report a bug. Need new features? `Contact us here`_ .. _contact us here: https://micropyramid.com/contact-us/ + .. _Demo: https://django-crm.micropyramid.com/ + diff --git a/accounts/migrations/0003_auto_20180410_1412.py b/accounts/migrations/0003_auto_20180410_1412.py new file mode 100644 index 00000000..a971054d --- /dev/null +++ b/accounts/migrations/0003_auto_20180410_1412.py @@ -0,0 +1,17 @@ +# Generated by Django 2.0 on 2018-04-10 08:42 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0002_auto_20170926_1752'), + ] + + operations = [ + migrations.AlterModelOptions( + name='account', + options={'ordering': ['-created_on']}, + ), + ] diff --git a/accounts/views.py b/accounts/views.py index e4ed4ee0..9eda6386 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -25,7 +25,7 @@ def accounts_list(request): industry = request.POST.get('industry') if name: - accounts_list = Account.objects.filter(name__icontains=name) + accounts_list = accounts_list.filter(name__icontains=name) if city: accounts_list = accounts_list.filter( billing_address__in=[i.id for i in Address.objects.filter(city__contains=city)]) diff --git a/app.json b/app.json deleted file mode 100644 index 10ab1cab..00000000 --- a/app.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "Django-CRM", - "description": "Customer Relationship Management Dashboard", - "repository": "https://github.com/MicroPyramid/Django-CRM.git", - "addons": ["heroku-postgresql:hobby-dev"], - "scripts": { - "postdeploy": "python manage.py makemigrations && python manage.py migrate && echo \"from common.models import User; User.objects.create_superuser('Admin001', 'admin001')\" | python manage.py shell" - }, - "env": {} -} \ No newline at end of file diff --git a/cases/migrations/0003_auto_20180410_1412.py b/cases/migrations/0003_auto_20180410_1412.py new file mode 100644 index 00000000..a40d2542 --- /dev/null +++ b/cases/migrations/0003_auto_20180410_1412.py @@ -0,0 +1,17 @@ +# Generated by Django 2.0 on 2018-04-10 08:42 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('cases', '0002_auto_20170926_1752'), + ] + + operations = [ + migrations.AlterModelOptions( + name='case', + options={'ordering': ['-created_on']}, + ), + ] diff --git a/cases/views.py b/cases/views.py index 81024a9d..1c5cc204 100644 --- a/cases/views.py +++ b/cases/views.py @@ -25,7 +25,7 @@ def cases_list(request): if not account: account = 0 if name: - cases = Case.objects.filter(name__contains=name) + cases = cases.filter(name__contains=name) if account: cases = cases.filter(account=account) if status: diff --git a/common/forms.py b/common/forms.py index 01e6d8b4..eb4e98ad 100644 --- a/common/forms.py +++ b/common/forms.py @@ -119,3 +119,20 @@ class LoginForm(forms.ModelForm): raise forms.ValidationError("Invalid email and password") return self.cleaned_data + +class ChangePasswordForm(forms.Form): + CurrentPassword = forms.CharField(max_length=100) + Newpassword = forms.CharField(max_length=100) + confirm = forms.CharField(max_length=100) + + def __init__(self, *args, **kwargs): + super(ChangePasswordForm, self).__init__(*args, **kwargs) + + def clean_confirm(self): + if len(self.data.get('confirm')) < 4: + raise forms.ValidationError( + 'Password must be at least 4 characters long!') + if self.data.get('confirm') != self.cleaned_data.get('Newpassword'): + raise forms.ValidationError( + 'Confirm password do not match with new password') + return self.data.get('confirm') diff --git a/common/migrations/0003_auto_20180321_1135.py b/common/migrations/0003_auto_20180321_1135.py deleted file mode 100644 index 55cd99ed..00000000 --- a/common/migrations/0003_auto_20180321_1135.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.5 on 2018-03-21 06:05 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('common', '0002_auto_20170926_1752'), - ] - - operations = [ - migrations.AddField( - model_name='user', - name='first_name', - field=models.CharField(blank=True, max_length=150), - ), - migrations.AddField( - model_name='user', - name='last_name', - field=models.CharField(blank=True, max_length=150), - ), - ] diff --git a/common/views.py b/common/views.py index a2529cab..7f5a8e68 100644 --- a/common/views.py +++ b/common/views.py @@ -3,9 +3,10 @@ from django.contrib.auth.decorators import login_required from django.views.decorators.csrf import csrf_exempt from django.http import HttpResponseRedirect, JsonResponse from django.contrib.auth import logout, authenticate, login +from django.contrib.auth.hashers import check_password from common.models import User -from common.forms import UserForm, LoginForm +from common.forms import UserForm, LoginForm, ChangePasswordForm @login_required @@ -15,13 +16,29 @@ def home(request): @login_required def change_pass(request): - return render(request, 'change_password.html') + error, errors = "", "" + form = ChangePasswordForm() + if request.method == 'POST': + form = ChangePasswordForm(request.POST) + if form.is_valid(): + user = request.user + if not check_password(request.POST['CurrentPassword'], user.password): + error = "Invalid old password" + else: + user.set_password(request.POST.get('Newpassword')) + user.is_active = True + user.save() + return HttpResponseRedirect('/') + else: + errors = form.errors + return render(request, "change_password.html", {'error': error, + 'errors': errors}) @login_required def profile(request): user = request.user - user_obj = User.objects.filter(id=user.id) + user_obj = User.objects.get(id=user.id) return render(request, "profile.html", {'user_obj': user_obj}) @csrf_exempt @@ -64,13 +81,13 @@ def users_list(request): active_users = User.objects.filter(is_active=True) inactive_users = User.objects.filter(is_active=False) if first_name: - users_list = User.objects.filter(first_name__icontains=first_name) + users_list = users_list.filter(first_name__icontains=first_name) if last_name: - users_list = User.objects.filter(last_name__icontains=last_name) + users_list = users_list.filter(last_name__icontains=last_name) if username: - users_list = User.objects.filter(username__icontains=username) + users_list = users_list.filter(username__icontains=username) if email: - users_list = User.objects.filter(email__icontains=email) + users_list = users_list.filter(email__icontains=email) return render(request, "users/list.html", { 'users': users_list, 'active_users': active_users, 'per_page': page, 'inactive_users': inactive_users @@ -117,7 +134,10 @@ def edit_user(request, user_id): user_form = UserForm(request.POST, instance=user_obj) if user_form.is_valid(): user_form.save() - return redirect("common:users_list") + if not request.user.is_staff: + return redirect("common:profile") + else: + return redirect("common:users_list") else: return render(request, 'users/create.html', {'user_form': user_form, "errors": user_form.errors}) else: diff --git a/crm/settings.py b/crm/settings.py index 94c045dc..110d0358 100644 --- a/crm/settings.py +++ b/crm/settings.py @@ -1,5 +1,4 @@ import os -import dj_database_url # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -73,25 +72,19 @@ WSGI_APPLICATION = 'crm.wsgi.application' # Database # https://docs.djangoproject.com/en/1.10/ref/settings/#databases -if 'DATABASE_URL' in os.environ: - DATABASES = { - 'default': dj_database_url.config( - default='sqlite:////{0}'.format( - os.path.join(BASE_DIR, 'db.sqlite3')) - ) - } - STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage' - STATIC_ROOT = (os.path.join(BASE_DIR, "static")) -else: - DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), - } +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'dj_crm', + 'USER': 'postgres', + 'PASSWORD': 'root', + 'HOST': '127.0.0.1', + 'PORT': '5432' } +} - STATICFILES_DIRS = [os.path.join(BASE_DIR, "static"), ] +STATICFILES_DIRS = [os.path.join(BASE_DIR, "static"), ] # Password validation diff --git a/crm/wsgi.py b/crm/wsgi.py index 340bc438..f10a4dfd 100644 --- a/crm/wsgi.py +++ b/crm/wsgi.py @@ -1,7 +1,6 @@ import os import sys from django.core.wsgi import get_wsgi_application -from whitenoise.django import DjangoWhiteNoise PROJECT_DIR = os.path.abspath(__file__) @@ -11,4 +10,3 @@ sys.path.append(PROJECT_DIR) os.environ.setdefault("DJANGO_SETTINGS_MODULE", "crm.settings") application = get_wsgi_application() -application = DjangoWhiteNoise(application) diff --git a/docs/source/index.rst b/docs/source/index.rst index f507102d..a3c5765c 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -32,10 +32,9 @@ 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 Visit our Django web development page [Here](https://micropyramid.com/django-ecommerce-development/) diff --git a/emails/migrations/0002_auto_20180410_1412.py b/emails/migrations/0002_auto_20180410_1412.py new file mode 100644 index 00000000..4f6577f7 --- /dev/null +++ b/emails/migrations/0002_auto_20180410_1412.py @@ -0,0 +1,17 @@ +# Generated by Django 2.0 on 2018-04-10 08:42 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('emails', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='email', + options={'ordering': ['-id']}, + ), + ] diff --git a/emails/models.py b/emails/models.py index 22ba4c30..b0567cfd 100644 --- a/emails/models.py +++ b/emails/models.py @@ -16,3 +16,6 @@ class Email(models.Model): def __unicode__(self): return self.timedelta + + class Meta: + ordering = ['-id'] diff --git a/leads/migrations/0002_auto_20180410_1412.py b/leads/migrations/0002_auto_20180410_1412.py new file mode 100644 index 00000000..43f4d438 --- /dev/null +++ b/leads/migrations/0002_auto_20180410_1412.py @@ -0,0 +1,17 @@ +# Generated by Django 2.0 on 2018-04-10 08:42 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('leads', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='lead', + options={'ordering': ['-created_on']}, + ), + ] diff --git a/leads/views.py b/leads/views.py index 78ec6c75..9e64a0bd 100644 --- a/leads/views.py +++ b/leads/views.py @@ -12,31 +12,32 @@ from common.models import User, Address, Comment, Team from common.utils import LEAD_STATUS, LEAD_SOURCE, INDCHOICES, TYPECHOICES, COUNTRIES from leads.forms import LeadCommentForm, LeadForm from accounts.forms import AccountForm -from common.forms import BillingAddressForm +from common.forms import BillingAddressForm, ShippingAddressForm from accounts.models import Account from planner.models import Event, Reminder from planner.forms import ReminderForm +from opportunity.forms import OpportunityForm # CRUD Operations Start @login_required def leads_list(request): - lead_obj = Lead.objects.all() + lead_obj = Lead.objects.all().exclude(status='converted') page = request.POST.get('per_page') first_name = request.POST.get('first_name') last_name = request.POST.get('last_name') city = request.POST.get('city') email = request.POST.get('email') if first_name: - lead_obj = Lead.objects.filter(first_name__icontains=first_name) + lead_obj = lead_obj.filter(first_name__icontains=first_name) if last_name: - lead_obj = Lead.objects.filter(last_name__icontains=last_name) + lead_obj = lead_obj.filter(last_name__icontains=last_name) if city: - lead_obj = Lead.objects.filter(address=Address.objects.filter + lead_obj = lead_obj.filter(address=Address.objects.filter (city__icontains=city)) if email: - lead_obj = Lead.objects.filter(email__icontains=email) + lead_obj = lead_obj.filter(email__icontains=email) return render(request, 'leads/leads.html', { 'lead_obj': lead_obj, 'per_page': page}) @@ -66,10 +67,12 @@ def add_lead(request): lead_obj.assigned_to.add(*assignedto_list) lead_obj.teams.add(*teams_list) if request.POST.get('status') == "converted": - Account.objects.create( + account_object = Account.objects.create( created_by=request.user, name=lead_account, email=lead_email, phone=lead_phone ) + account_object.billing_address = address_object + account_object.save() if request.POST.get("savenewform"): return HttpResponseRedirect(reverse("leads:add_lead")) else: @@ -144,10 +147,12 @@ def edit_lead(request, lead_id): lead_obj.teams.clear() lead_obj.teams.add(*teams_list) if request.POST.get('status') == "converted": - Account.objects.create( + account_object = Account.objects.create( created_by=request.user, name=lead_account, email=lead_email, phone=lead_phone ) + account_object.billing_address = dis_address_obj + account_object.save() return HttpResponseRedirect(reverse('leads:list')) else: return render(request, 'leads/create_lead.html', { diff --git a/opportunity/migrations/0002_auto_20180410_1412.py b/opportunity/migrations/0002_auto_20180410_1412.py new file mode 100644 index 00000000..bb0beccd --- /dev/null +++ b/opportunity/migrations/0002_auto_20180410_1412.py @@ -0,0 +1,17 @@ +# Generated by Django 2.0 on 2018-04-10 08:42 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('opportunity', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='opportunity', + options={'ordering': ['-created_on']}, + ), + ] diff --git a/opportunity/views.py b/opportunity/views.py index b2ba446f..f8770f5a 100644 --- a/opportunity/views.py +++ b/opportunity/views.py @@ -28,7 +28,7 @@ def opp_list(request): opp_stage = request.POST.get('stage') opp_source = request.POST.get('lead_source') if opp_name: - opportunity_list = Opportunity.objects.filter(name__contains=opp_name) + opportunity_list = opportunity_list.filter(name__contains=opp_name) if opp_stage: opportunity_list = opportunity_list.filter(stage=opp_stage) if opp_source: diff --git a/planner/migrations/0002_auto_20180410_1412.py b/planner/migrations/0002_auto_20180410_1412.py new file mode 100644 index 00000000..a54970b4 --- /dev/null +++ b/planner/migrations/0002_auto_20180410_1412.py @@ -0,0 +1,20 @@ +# Generated by Django 2.0 on 2018-04-10 08:42 + +from django.db import migrations, models +import django.db.models.deletion +import django.db.models.query_utils + + +class Migration(migrations.Migration): + + dependencies = [ + ('planner', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='event', + name='content_type', + field=models.ForeignKey(blank=True, choices=[(10, 'Account'), (13, 'Lead'), (14, 'Opportunity'), (11, 'Case')], limit_choices_to=django.db.models.query_utils.Q(django.db.models.query_utils.Q(('app_label', 'account'), ('model', 'Account'), ('id', 10), _connector='AND'), django.db.models.query_utils.Q(('app_label', 'leads'), ('model', 'Lead'), ('id', 13), _connector='AND'), django.db.models.query_utils.Q(('app_label', 'opportunity'), ('model', 'Opportunity'), ('id', 14), _connector='AND'), django.db.models.query_utils.Q(('app_label', 'cases'), ('model', 'Case'), ('id', 11), _connector='AND'), _connector='OR'), null=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'), + ), + ] diff --git a/requirements.txt b/requirements.txt index d06c7994..01513008 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,9 +2,6 @@ Django==2.0 django-simple-pagination==1.1.8 pytz==2018.3 psycopg2==2.7.4 -gunicorn==19.7.1 -whitenoise==3.3.1 -dj-database-url==0.4.2 redis django-compressor==2.2 -coverage \ No newline at end of file +coverage diff --git a/static/css/main.scss b/static/css/main.scss index d3d80d14..9846c606 100644 --- a/static/css/main.scss +++ b/static/css/main.scss @@ -634,3 +634,29 @@ $color_2: #000; list-style-type: none; padding: 0px; } + +.view-pad{ +padding:0px !important; + h5{ + width: 79%; + margin-bottom: 0; + position: relative; + font-size: 13px; + display: inline-block; + text-align: left; + padding-top: 0px; + } + a{ + float: right; + margin-right: 95px; + border-radius: 0; + font-size: 12px; + padding: 3px 10px; + color: #fff; + margin-top: -4px; + &:hover{ + text-decoration:none; + } + } + +} diff --git a/static/js/accounts/acc_comment.js b/static/js/accounts/acc_comment.js deleted file mode 100644 index 2d1125be..00000000 --- a/static/js/accounts/acc_comment.js +++ /dev/null @@ -1,108 +0,0 @@ - -/*Cookie Js Starts*/ - -function getCookie(name) { - var cookieValue = null; - if (document.cookie && document.cookie != '') { - var cookies = document.cookie.split(';'); - for (var i = 0; i < cookies.length; i++) { - var cookie = jQuery.trim(cookies[i]); - // Does this cookie string begin with the name we want? - if (cookie.substring(0, name.length + 1) == (name + '=')) { - cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); - break; - } - } - } - return cookieValue; -} - -/*Cookie Js Ends*/ - -/*Comment Add Js Start*/ - -$("#comment_form").submit(function(e){ - e.preventDefault() - var formData = new FormData($("#comment_form")[0]); - $.ajax({ - url : "/accounts/comment/add/", - type : "POST", - data : formData, - cache: false, - contentType: false, - processData: false, - success: function(data){ - if(data.error){ - $("#CommentError").html(data.error).show() - } - else { - $("#comments_div").prepend("
" + data.comment + "
"+data.comment+"
"+data.commented_by+"
"+d.toGMTString()+"
"+data.comment+"
"+data.commented_by+"
"+d.toGMTString()+"
"+data.comment+"
"+data.commented_by+"
"+d.toGMTString()+"
"+data.comment+"
"+data.commented_by+"
"+d.toGMTString()+"
"+data.comment+"
"+data.commented_by+"
"+d.toGMTString()+"