diff --git a/Dockerfile b/Dockerfile index 2c7d94d..c7ae82b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,8 @@ RUN apt-get update && apt-get install -y \ WORKDIR /usr/src/app +COPY koalixcrm /usr/src/app/koalixcrm + # Install dependencies RUN pip install --upgrade pip COPY requirements.txt /usr/src/app/ @@ -33,4 +35,4 @@ ENV JAVA_HOME /usr/lib/jvm/java-17-openjdk-amd64 EXPOSE 8000 # Command to run the Django development server -CMD python manage.py migrate && gunicorn --bind :8000 koalixcrm.wsgi \ No newline at end of file +CMD python manage.py koalixcrm/migrate && gunicorn --bind :8000 wsgi \ No newline at end of file diff --git a/README.md b/README.md index a779df0..83bebd8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,54 @@ -# koalixcrm-prod-container -In this repo we define and build the kaolixcrm prod container +# Koalix CRM Production Docker Container + +This repository contains the Docker configuration for creating a production-ready container for the Koalix CRM system. +The Koalix CRM is a small and easy-to-use Customer Relationship Management system, including a basic Accounting Software. + +This Docker container is configured with all necessary dependencies and settings, allowing for the rapid deployment of +the Koalix CRM in a production environment. + +## Installation + +Before you start, you need both Docker and Docker Compose installed on your system. Docker Compose comes pre-installed +with Docker for Mac and Windows, but for Linux, you need to install it separately after Docker. + +For Windows and Mac users, you can download Docker Desktop from +[Docker's official website](https://www.docker.com/products/docker-desktop). +For Linux users, instructions vary by distribution. Docker's official website provides detailed instructions +for [Ubuntu](https://docs.docker.com/engine/install/ubuntu/), [Debian](https://docs.docker.com/engine/install/debian/), +[Fedora](https://docs.docker.com/engine/install/fedora/), and [CentOS](https://docs.docker.com/engine/install/centos/). + +## Configuration + +Download the `docker-compose.yaml` file from this repository and place it in a directory where you'd like to run the +Koalix CRM container. + +Before running the containers, adjust a few environment variables in the `docker-compose.yaml` file: + +- `DJANGO_SECRET_KEY`: A secret key for a particular Django installation. This is used to provide cryptographic signing, +- and should be set to a unique, unpredictable value. +- `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB`: These are the root username, password, and the database name that +- PostgreSQL will set up at its launch. + +Replace the placeholders with actual values. + +## Running the Application + +Open a terminal and navigate (`cd`) to the directory containing the `docker-compose.yaml` file. Use the following +command to download all the required images: + +docker-compose pull +After the images are downloaded, launch the application using: + +docker-compose up + +The application should then be accessible at `http://localhost:8000`, or the HOST and PORT you specified. + +## Audience + +This repository is intended for DevOps engineers, software developers, or IT administrators who need to deploy +Koalix CRM in a production environment quickly and efficiently. + +## Feedback + +We welcome your feedback and contributions! Please submit issues or pull requests if you have improvements +or find problems. \ No newline at end of file diff --git a/koalixcrm/__init__.py b/koalixcrm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/koalixcrm/manage.py b/koalixcrm/manage.py new file mode 100644 index 0000000..1cf4323 --- /dev/null +++ b/koalixcrm/manage.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + try: + from django.core.management import execute_from_command_line + except ImportError: + # The above import may fail for some other reason. Ensure that the + # issue is really that Django is missing to avoid masking other + # exceptions on Python 2. + try: + import django + except ImportError: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) + raise + execute_from_command_line(sys.argv) diff --git a/koalixcrm/settings/__init__.py b/koalixcrm/settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/koalixcrm/settings/base_settings.py b/koalixcrm/settings/base_settings.py new file mode 100644 index 0000000..ad9a1e3 --- /dev/null +++ b/koalixcrm/settings/base_settings.py @@ -0,0 +1,123 @@ +""" +Django base settings for koalixcrm project. +""" + +import os + +BASE_DIR = os.path.dirname(os.path.dirname(__file__)) + +# Application definition +PREREQUISITE_APPS = [ + 'django.contrib.contenttypes', + 'grappelli.dashboard', + 'grappelli', + 'filebrowser', + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'rest_framework', + 'django_filters' +] + +PROJECT_APPS = [ + 'koalixcrm.crm', + 'koalixcrm.accounting', + 'koalixcrm.djangoUserExtension', + 'koalixcrm.subscriptions', +] + +INSTALLED_APPS = PREREQUISITE_APPS + PROJECT_APPS + +KOALIXCRM_PLUGINS = ( + 'koalixcrm.subscriptions', +) + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'koalixcrm.crm.middleware.timezoneMiddleware.TimezoneMiddleware', +] + +ROOT_URLCONF = 'settings.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [os.path.join(BASE_DIR, 'templates')], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'settings.wsgi.application' + + +# Password validation +# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + +LANGUAGE_CODE = 'en-us' +TIME_ZONE = 'UTC' +USE_I18N = True +USE_L10N = True +USE_TZ = True + +STATIC_URL = '/static/' +STATIC_ROOT = os.path.join(BASE_DIR, 'static') + +MEDIA_URL = "/media/" +MEDIA_ROOT = os.path.join(BASE_DIR) + +PROJECT_ROOT = BASE_DIR + +# Settings specific for koalixcrm +PDF_OUTPUT_ROOT = os.path.join(STATIC_ROOT, 'pdf') + +# Settings specific for filebrowser +FILEBROWSER_DIRECTORY = 'media/uploads/' +FILEBROWSER_EXTENSIONS = { + 'XML': ['.xml'], + 'XSL': ['.xsl'], + 'JPG': ['.jpg'], + 'PNG': ['.png'], + 'GIF': ['.gif'], + 'TTF': ['.ttf'], +} + +LOGIN_URL = "/admin/login" + +REST_FRAMEWORK = { + 'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',), + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework.authentication.BasicAuthentication', + 'rest_framework.authentication.SessionAuthentication', + ) +} diff --git a/koalixcrm/settings/dashboard.py b/koalixcrm/settings/dashboard.py new file mode 100644 index 0000000..ada1d9a --- /dev/null +++ b/koalixcrm/settings/dashboard.py @@ -0,0 +1,203 @@ +""" +This file was generated with the custom dashboard management command and +contains the class for the main dashboard. + +To activate your index dashboard add the following to your settings.py:: + GRAPPELLI_INDEX_DASHBOARD = 'koalixcrm.dashboard.CustomIndexDashboard' +""" + +from django.utils.translation import gettext_lazy as _ +from grappelli.dashboard import modules, Dashboard +from grappelli.dashboard.utils import get_admin_site_name +from koalixcrm.version import KOALIXCRM_VERSION + + +class CustomIndexDashboard(Dashboard): + """ + Custom index dashboard for www. + """ + + def init_with_context(self, context): + self.children.append(modules.Group( + _('koalixcrm Version' + KOALIXCRM_VERSION), + column=1, + collapsible=True, + children=[ + modules.ModelList( + _('Sales Documents and Contracts'), + column=1, + css_classes=('collapse closed',), + models=('koalixcrm.crm.documents.contract.Contract', + 'koalixcrm.crm.documents.quote.Quote', + 'koalixcrm.crm.documents.purchase_confirmation.PurchaseConfirmation', + 'koalixcrm.crm.documents.delivery_note.DeliveryNote', + 'koalixcrm.crm.documents.invoice.Invoice', + 'koalixcrm.crm.documents.payment_reminder.PaymentReminder',), + ), + modules.ModelList( + _('Scheduler'), + column=1, + css_classes=('collapse closed',), + models=('koalixcrm.crm.contact.contact.CallForContact', + 'koalixcrm.crm.contact.contact.VisitForContact',), + ), + modules.ModelList( + _('Products'), + column=1, + css_classes=('collapse closed',), + models=('koalixcrm.crm.product.product.Product',), + ), + modules.ModelList( + _('Contacts'), + column=1, + css_classes=('collapse closed',), + models=('koalixcrm.crm.contact.customer.Customer', + 'koalixcrm.crm.contact.supplier.Supplier', + 'koalixcrm.crm.contact.person.Person',), + ), + modules.ModelList( + _('Accounting'), + column=1, + css_classes=('collapse closed',), + models=('koalixcrm.accounting.*',), + ), + modules.ModelList( + _('Projects'), + column=1, + css_classes=('collapse closed',), + models=('koalixcrm.crm.reporting.project.Project', + 'koalixcrm.crm.reporting.reporting_period.ReportingPeriod', + 'koalixcrm.crm.reporting.task.Task', + 'koalixcrm.crm.reporting.agreement.Agreement', + 'koalixcrm.crm.reporting.estimation.Estimation', + 'koalixcrm.crm.reporting.human_resource.HumanResource', + 'koalixcrm.crm.documents.purchase_order.PurchaseOrder',), + ), + modules.LinkList( + _('Report Work And Expenses'), + column=1, + children=[{'title': _('Time Tracking'), + 'url': '/koalixcrm/crm/reporting/time_tracking/', + 'external': False}, + {'title': _('Set Timezone'), + 'url': '/koalixcrm/crm/reporting/set_timezone/', + 'external': False}] + ) + + ] + )) + + # append a group for "Administration" & "Applications" + self.children.append(modules.Group( + _('Users, Access Rights and Application Settings'), + column=1, + collapsible=True, + children=[ + modules.ModelList( + _('Administration'), + column=1, + collapsible=False, + models=('django.contrib.*',), + ), + modules.ModelList( + _('Contact settings'), + column=1, + css_classes=('collapse closed',), + models=('koalixcrm.crm.contact.customer_billing_cycle.CustomerBillingCycle', + 'koalixcrm.crm.contact.customer_group.CustomerGroup',), + ), + modules.ModelList( + _('Product settings'), + column=1, + css_classes=('collapse closed',), + models=('koalixcrm.crm.product.tax.Tax', + 'koalixcrm.crm.product.unit.Unit', + 'koalixcrm.crm.product.currency.Currency'), + ), + modules.ModelList( + _('Reporting settings'), + column=1, + css_classes=('collapse closed',), + models=('koalixcrm.crm.reporting.agreement_status.AgreementStatus', + 'koalixcrm.crm.reporting.agreement_type.AgreementType', + 'koalixcrm.crm.reporting.estimation_status.EstimationStatus', + 'koalixcrm.crm.reporting.generic_project_link.GenericProjectLink', + 'koalixcrm.crm.reporting.generic_task_link.GenericTaskLink', + 'koalixcrm.crm.reporting.project_link_type.ProjectLinkType', + 'koalixcrm.crm.reporting.project_status.ProjectStatus', + 'koalixcrm.crm.reporting.reporting_period_status.ReportingPeriodStatus', + 'koalixcrm.crm.reporting.resource.Resource', + 'koalixcrm.crm.reporting.resource_manager.ResourceManager', + 'koalixcrm.crm.reporting.resource_type.ResourceType', + 'koalixcrm.crm.reporting.task_link_type.TaskLinkType', + 'koalixcrm.crm.reporting.task_status.TaskStatus',), + ), + modules.ModelList( + _('PDF document settings'), + column=1, + css_classes=('collapse closed',), + models=('koalixcrm.djangoUserExtension.user_extension.document_template.*', + 'koalixcrm.djangoUserExtension.user_extension.template_set.TemplateSet', + 'koalixcrm.djangoUserExtension.user_extension.user_extension.*',), + ), + ] + )) + + # append another link list module for "support". + self.children.append(modules.LinkList( + _('Media Management'), + column=2, + children=[ + { + 'title': _('FileBrowser'), + 'url': '/admin/filebrowser/browse/', + 'external': False, + }, + ] + )) + + # append another link list module for "support". + self.children.append(modules.LinkList( + _('Support'), + column=2, + children=[ + { + 'title': _('koalixcrm on github'), + 'url': 'https://github.com/scaphilo/koalixcrm/', + 'external': True, + }, + { + 'title': _('Django Documentation'), + 'url': 'http://docs.djangoproject.com/', + 'external': True, + }, + { + 'title': _('Grappelli Documentation'), + 'url': 'http://packages.python.org/django-grappelli/', + 'external': True, + }, + { + 'title': _('Grappelli Google-Code'), + 'url': 'http://code.google.com/p/django-grappelli/', + 'external': True, + }, + ] + )) + + # append a feed module + self.children.append(modules.Feed( + _('Latest Django News'), + column=2, + feed_url='http://www.djangoproject.com/rss/weblog/', + limit=5 + )) + + # append a recent actions module + self.children.append(modules.RecentActions( + _('Recent Actions'), + limit=5, + collapsible=False, + column=3, + )) + + diff --git a/koalixcrm/settings/production_docker_postgres_settings.py b/koalixcrm/settings/production_docker_postgres_settings.py new file mode 100644 index 0000000..cf41ec6 --- /dev/null +++ b/koalixcrm/settings/production_docker_postgres_settings.py @@ -0,0 +1,29 @@ +""" +Django settings for koalixcrm project when used in productive environment. +""" + +from .base_settings import * + +SECRET_KEY = os.environ.get("SECRET_KEY") + +DEBUG = bool(os.environ.get("DEBUG", default=0)) + +# 'DJANGO_ALLOWED_HOSTS' should be a single string of hosts with a space between each. +# For example: 'DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]' +ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS").split(" ") + +DATABASES = { + "default": { + "ENGINE": os.environ.get("SQL_ENGINE", "django.db.backends.postgresql"), + "NAME": os.environ.get("SQL_DATABASE", BASE_DIR / "postgres"), + "USER": os.environ.get("SQL_USER", "postgres"), + "PASSWORD": os.environ.get("SQL_PASSWORD", "password"), + "HOST": os.environ.get("SQL_HOST", "localhost"), + "PORT": os.environ.get("SQL_PORT", "5432"), + } +} + +FOP_EXECUTABLE = "/usr/bin/fop-2.2/fop/fop" +GRAPPELLI_INDEX_DASHBOARD = 'settings.dashboard.CustomIndexDashboard' +FILEBROWSER_CONVERT_FILENAME = False +KOALIXCRM_REST_API_AUTH = True \ No newline at end of file diff --git a/koalixcrm/settings/urls.py b/koalixcrm/settings/urls.py new file mode 100644 index 0000000..d81637d --- /dev/null +++ b/koalixcrm/settings/urls.py @@ -0,0 +1,66 @@ +"""test_koalixcrm URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.11/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.conf.urls import url, include + 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) +""" + +from django.urls import path +from django.conf.urls.static import * +from django.contrib.staticfiles.urls import static +from django.contrib import admin +from django.shortcuts import redirect +from django.conf.urls import include, url +from filebrowser.sites import site +from rest_framework import routers + +from koalixcrm.accounting.rest.restinterface import AccountAsJSON, AccountingPeriodAsJSON, BookingAsJSON, \ + ProductCategoryAsJSON +from koalixcrm.crm.rest.restinterface import ContractAsJSON, CurrencyAsJSON, ProductAsJSON, ProjectAsJSON, TaskAsJSON, \ + TaskStatusAsJSON, TaxAsJSON, UnitAsJSON, CustomerGroupAsJSON, CustomerBillingCycleAsJSON, \ + CustomerAsJSON, ContactPostalAddressAsJSON, ContactEmailAddressAsJSON, ContactPhoneAddressAsJSON, \ + ProjectStatusAsJSON + +router = routers.DefaultRouter() +router.register(r'accounts', AccountAsJSON) +router.register(r'accountingPeriods', AccountingPeriodAsJSON) +router.register(r'bookings', BookingAsJSON) +router.register(r'contracts', ContractAsJSON) +router.register(r'currencies', CurrencyAsJSON) +router.register(r'customers', CustomerAsJSON) +router.register(r'customerBillingCycles', CustomerBillingCycleAsJSON) +router.register(r'contactPostalAddresses', ContactPostalAddressAsJSON) +router.register(r'contactPhoneNumbers', ContactPhoneAddressAsJSON) +router.register(r'contactEmailAddresses', ContactEmailAddressAsJSON) +router.register(r'customerGroups', CustomerGroupAsJSON) +router.register(r'products', ProductAsJSON) +router.register(r'productCategories', ProductCategoryAsJSON) +router.register(r'projects', ProjectAsJSON) +router.register(r'projectStatus', ProjectStatusAsJSON) +router.register(r'tasks', TaskAsJSON) +router.register(r'taskstatus', TaskStatusAsJSON) +router.register(r'taxes', TaxAsJSON) +router.register(r'units', UnitAsJSON) + +admin.autodiscover() + +urlpatterns = [ + path('', lambda _: redirect('admin:index'), name='index'), + path('', include(router.urls)), + path('admin/filebrowser/', site.urls), + path('grappelli/', include('grappelli.urls')), + path('koalixcrm/crm/reporting/', include('koalixcrm.crm.reporting.urls')), + path('admin/', admin.site.urls), + path('api-auth/', include('rest_framework.urls')), +] +urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + diff --git a/koalixcrm/settings/wsgi.py b/koalixcrm/settings/wsgi.py new file mode 100644 index 0000000..855a355 --- /dev/null +++ b/koalixcrm/settings/wsgi.py @@ -0,0 +1,19 @@ +""" +WSGI config for koalixcrm project. + +This module contains the WSGI application used by Django's development server +and any production WSGI deployments. It should expose a module-level variable +named `application`. Django's `runserver` and `runfcgi` commands discover +this application via the `WSGI_APPLICATION` setting. + +Usually this will be called "koalixcrm.wsgi". +""" + +import os +from django.core.wsgi import get_wsgi_application + +# The settings module that Django uses. By convention, it is usually in the form "myproject.settings.production" +os.environ.setdefault("DJANGO_SETTINGS_MODULE", + "settings.production_docker_postgres_settings") + +application = get_wsgi_application() \ No newline at end of file