Commit c8d0c848 authored by Carolina's avatar Carolina

Merge branch 'master' into 'staging'

Updating staging

See merge request !24
parents 8cc59412 36078084
Pipeline #8521 passed with stages
in 11 minutes and 29 seconds
......@@ -4,7 +4,7 @@ version: 2
jobs:
build:
docker:
- image: circleci/python:3.6-jessie-node
- image: circleci/python:3.6-node
- image: circleci/postgres:9.5
environment:
POSTGRES_USER: circleci
......
......@@ -9,6 +9,11 @@ DB_USER=
DB_PASSWORD=
ENABLE_EMAILS=True
EMAIL_HOST=
EMAIL_HOST_USER=
EMAIL_HOST_PASSWORD=
EMAIL_PORT=
EMAIL_USE_TLS=
ALLOWED_HOSTS=localhost
......@@ -18,3 +23,13 @@ RECAPTCHA_PUBLIC_KEY=
RECAPTCHA_PUBLIC_KEY=
SECRET_KEY=
USE_S3=
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_STORAGE_BUCKET_NAME=
AWS_S3_REGION_NAME=
AWS_DEFAULT_ACL=
COMPRESS_OFFLINE=
COMPRESS_ENABLED=
......@@ -109,3 +109,9 @@ webpack-stats.json
*.dump
deploy.sh
# mediafiles
mediafiles/
# vscode
.vscode/
\ No newline at end of file
......@@ -5,15 +5,16 @@ stages:
- test
services:
- postgres:latest
- postgres
- docker:stable-dind
variables:
POSTGRES_DB: misuper
POSTGRES_USER: runner
POSTGRES_PASSWORD: ""
POSTGRES_PASSWORD: password
DOCKER_DRIVER: overlay2
IMAGE_TAG: git.gob.cl:4567/super/misuper:$CI_COMMIT_REF_NAME
CI_DEBUG_TRACE: "true"
build:
stage: build
......@@ -28,13 +29,19 @@ build:
test:
stage: test
image: node:10.12.0-alpine
script:
- docker login git.gob.cl:4567 -u gitlab-ci-token -p $CI_BUILD_TOKEN
- docker pull $IMAGE_TAG
- docker run
-e DOCKER=True
-e DB_HOST=postgres
-e DB_NAME=$POSTGRES_DB
-e DB_USER=$POSTGRES_USER
-e DB_PASSWORD=$POSTGRES_PASSWORD
$IMAGE_TAG python manage.py test
- apk add python3=3.6.9-r1 python3-dev
- apk add build-base make gcc g++ libressl-dev libffi-dev
- apk add musl-dev postgresql-dev jpeg-dev zlib-dev gettext
- npm config set unsafe-perm true
- npm i npm@latest -g
- npm install
- npm run build
- pip3 install pipenv
- pipenv --python 3.6
- pipenv install
- pipenv run python manage.py collectstatic --noinput
- export DATABASE_URL=postgres://runner:password@postgres:5432/misuper
# -*- coding: utf-8 -*-
# Generated by Django 1.11.16 on 2019-05-31 14:54
from __future__ import unicode_literals
from django.db import migrations
from projects.enums import ProfilesKinds
def set_project_profiles(apps, schema_editor):
Project = apps.get_model('projects', 'Project')
ProjectColaborator = apps.get_model('projects', 'ProjectColaborator')
for project in Project.objects.all():
for viewer in project.projectviewer_set.all():
ProjectColaborator.objects.get_or_create(
user=viewer.user,
project=viewer.project,
association_time=viewer.association_time,
profile=ProfilesKinds.PROJECT_VIEWER,
)
for manager in project.projectmanager_set.all():
ProjectColaborator.objects.get_or_create(
user=manager.user,
project=manager.project,
association_time=manager.association_time,
profile=ProfilesKinds.PROJECT_MANAGER,
)
class Migration(migrations.Migration):
dependencies = [
('projects', '0042_auto_20190531_1054'),
]
operations = [
migrations.RunPython(set_project_profiles),
]
FROM node:10-alpine
FROM node:10.12.0-alpine
ARG DIRECTORY_PROJECT=/var/www/misuper
......@@ -10,7 +10,7 @@ COPY . $DIRECTORY_PROJECT
COPY project/local_settings.py.default $DIRECTORY_PROJECT/project/local_settings.py
# install python 3
RUN apk add --no-cache python3 python3-dev && \
RUN apk add --no-cache python3=3.6.9-r1 python3-dev && \
python3 -m ensurepip && \
rm -r /usr/lib/python*/ensurepip && \
pip3 install --upgrade pip setuptools && \
......@@ -38,5 +38,10 @@ RUN pipenv install --system --deploy --ignore-pipfile
# clean cache
RUN rm -r /root/.cache
# npm install
RUN npm install
RUN npm install --only=dev
RUN npm run build
# compile translations
RUN python manage.py compilemessages
......@@ -22,6 +22,7 @@ pypugjs = "==5.5.0"
pytz = ">=2018.4"
python-memcached = "*"
django-localflavor = "==2.1"
"psycopg2" = "*"
"psycopg2-binary" = "==2.7.6.1"
gunicorn = "==19.9.0"
django-cms = "==3.5.3"
......@@ -34,6 +35,11 @@ djangocms-bootstrap4 = "*"
djangocms-text-ckeditor = "*"
django-colorfield = "==0.1.15"
aldryn-apphooks-config = "==0.4.1"
django-storages = "==1.7.1"
boto3 = "==1.9.96"
djangorestframework-jwt = "*"
rollbar = "*"
docutils = "*"
[dev-packages]
......
This diff is collapsed.
# minecon-misuper
This project inherits from
[django-project-template-py3](https://github.com/magnet-cl/django-project-template-py3).
A web app to track the progress of permit requests with the chilean government
[![CircleCI](https://circleci.com/bb/magnet-cl/minecon-misuper/tree/master.svg?style=svg&circle-token=2b8e320191889b759e0baf57606a762f3434a86a)](https://circleci.com/bb/magnet-cl/minecon-misuper/tree/master)
[![CircleCI](https://circleci.com/bb/magnet-cl/minecon-misuper/tree/development.svg?style=svg&circle-token=2b8e320191889b759e0baf57606a762f3434a86a)](https://circleci.com/bb/magnet-cl/minecon-misuper/tree/development)
......@@ -64,3 +63,41 @@ on wait
* App.utils.thousandSeparator(): for a given number in text, returns the text
with thoushand separators (for spanish)
on wait
### Settings
To deploy this project, you need to configure settings in your environment
* DEBUG: if 'True', sets the project to debug mode
* SECRET_KEY: the secret key used to hash session cookies
* ADMINS: A list of tuples of name and email of the dev support admins
* LOCALLY_ALLOWED_HOSTS: A list of hosts the application allows "misuper.cl"
* ENABLE_EMAILS: if 'True', enables the sending of emails on the system
* EMAIL_HOST: Sets the host of the mailing service
* EMAIL_HOST_USER: Sets the user of the mailing service
* EMAIL_HOST_PASSWORD: Sets the password of the mailing service
* EMAIL_PORT: Sets the port of the mailing servic
* EMAIL_USE_TLS: if 'True', uses TLS when sending emails
* GOOGLE_ANALYTICS_CODE: The code for google analytics
* RECAPTCHA_PUBLIC_KEY: public Key for recaptcha service
* RECAPTCHA_PRIVATE_KEY: private key for recaptcha service
* USE_S3: If 'True', enables S3
* AWS_ACCESS_KEY_ID
* AWS_SECRET_ACCESS_KEY
* AWS_STORAGE_BUCKET_NAME
* AWS_S3_REGION_NAME
* AWS_DEFAULT_ACL
* COMPRESS_OFFLINE: if 'True', uses pre compiled static assets
* COMPRESS_ENABLED: if 'True', enables compiled static assets
* ROLLBAR_ACCESS_TOKEN
* CHILE_ATIENDE_ACCESS_TOKEN
* CLAVE_UNICA_CALLBACK: The url that clave única will call on login
* CLAVE_UNICA_CLIENT_ID
* CLAVE_UNICA_SECRET_KEY
* URL_PREFIX: A prefix to use on all sites
......@@ -20,10 +20,10 @@ class AddressForm(BaseModelForm):
"""
Form Address model.
"""
region = forms.ChoiceField(
help_text=_('Region'),
region = forms.ModelChoiceField(
label=_('Region'),
required=False
required=False,
queryset=Region.objects.all(),
)
widgets = {
......@@ -38,15 +38,22 @@ class AddressForm(BaseModelForm):
'latitude', 'longitude', 'polygon')
def __init__(self, *args, **kwargs):
instance = kwargs.get('instance', None)
if instance and instance.commune_id:
kwargs.update(initial={
'region': getattr(instance.commune, 'region'),
})
super(AddressForm, self).__init__(*args, **kwargs)
for field_name, field in self.fields.items():
field.widget.attrs['placeholder'] = field.help_text
region = self.fields['region']
region.choices = [(None, _('All'))]
region.choices.extend(
(o.id, str(o)) for o in Region.objects.all())
commune = self.fields['commune']
region.widget.attrs['class'] = 'form-control region-input'
commune.widget.attrs['class'] = 'form-control commune-input'
# removing hidden inputs labels
self.fields['latitude'].label = ''
......@@ -67,3 +74,9 @@ InlineAddressFormSet = inlineformset_factory(
form=AddressForm,
extra=1,
)
InlineAddressFormSetEdit = inlineformset_factory(
Project,
Address,
form=AddressForm,
extra=0,
)
......@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-31 16:00-0300\n"
"POT-Creation-Date: 2019-05-20 12:05-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......@@ -18,47 +18,47 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: apps.py:7 models.py:81
#: apps.py:7 models.py:91
msgid "addresses"
msgstr ""
#: forms.py:23
#: forms.py:24
msgid "Region"
msgstr "Región"
#: forms.py:38
msgid "All"
msgstr ""
#: models.py:23
#: models.py:27
msgid "commune"
msgstr "comuna"
#: models.py:28
#: models.py:32
msgid "label"
msgstr ""
#: models.py:34
#: models.py:38
msgid "street name"
msgstr "nombre calle"
#: models.py:39
#: models.py:43
msgid "address number"
msgstr "número dirección"
#: models.py:48
#: models.py:52
msgid "Ej: +56 9 9765 84 84, 9 8777828, 032 2 345654"
msgstr ""
#: models.py:72
#: models.py:76
msgid "default"
msgstr ""
#: models.py:80
msgid "polygon"
msgstr "polígono"
#: models.py:90
msgid "address"
msgstr ""
#: models.py:83
#: models.py:93
msgid "Can view addresses"
msgstr ""
......
......@@ -15,6 +15,10 @@ from regions.models import Commune
class Address(BaseModel):
"""
Este modelo sirve para guardar las múltiples direcciones
que puede tener un proyecto.
"""
# foreign key fields
commune = models.ForeignKey(
Commune,
......
......@@ -69,17 +69,10 @@ class CommuneSerializer(serializers.HyperlinkedModelSerializer):
fields = ('id', 'name')
class DocumentKindField(serializers.Field):
def to_representation(self, value):
return value.get_kind_display()
class CompanyDocumentSerializer(serializers.HyperlinkedModelSerializer):
kind = DocumentKindField(source='*', read_only=True)
class Meta:
model = CompanyDocument
fields = ('file', 'kind')
fields = ('file',)
class CompanyContactSerializer(serializers.HyperlinkedModelSerializer):
......@@ -113,21 +106,6 @@ class ProjectsField(serializers.Field):
return list(value.projects.all().values_list('cup', flat=True))
class CompanySerializer(serializers.HyperlinkedModelSerializer):
projects = ProjectsField(source='*', read_only=True)
petitioner = PetitionerField(source='*', read_only=True)
legal_representatives = UserSerializer(many=True, read_only=True)
documents = CompanyDocumentSerializer(many=True, read_only=True)
contacts = CompanyContactSerializer(many=True, read_only=True)
class Meta:
model = Company
fields = (
'valid', 'rut', 'name', 'projects', 'documents',
'contacts', 'legal_representatives', 'petitioner',
)
class CompanyProjectSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.IntegerField(label='ID')
......@@ -177,6 +155,7 @@ class ProjectSerializer(serializers.HyperlinkedModelSerializer):
project_managers = UserSerializer(many=True, read_only=True)
project_viewers = UserSerializer(many=True, read_only=True)
petitioner = ProjectPetitionerField(source='*', read_only=True)
projected_total_investment = serializers.SerializerMethodField()
class Meta:
model = Project
......@@ -200,6 +179,9 @@ class ProjectSerializer(serializers.HyperlinkedModelSerializer):
'petitioner',
)
def get_projected_total_investment(self, project):
return project.investment_amount
def create(self, validated_data):
# get company
company = validated_data.pop('company')
......
This diff is collapsed.
......@@ -7,34 +7,34 @@ from permit_requests import api_views as permit_requests_api_views
urlpatterns = [
url(
r'^companies/(?P<company_rut>[\w.-]+)/$',
r'^companies/(?P<company_rut>[\w.-]+)/?$',
views.PetitionerCompanyValidationView.as_view(),
name='company_api_detail',
),
url(
r'^companies/(?P<company_rut>[\w.-]+)/projects/(?P<cup>.+)/$',
r'^companies/(?P<company_rut>[\w.-]+)/projects/(?P<cup>.+)/?$',
views.ValidateCUPView.as_view(),
name='project_api_validate_cup',
),
url(
r'^projects/(?P<cup>.+)/$',
r'^projects/(?P<cup>[\w]+)/?$',
views.PetitionerProjectValidationView.as_view(),
name='project_api_detail',
),
url(
r'^applications/$',
r'^applications/?$',
permit_requests_api_views.CreatePermitRequestView.as_view(),
name='permitrequest_api_create',
),
url(
r'^applications/(?P<pk>[\d]+)/$',
r'^applications/(?P<pk>[\d]+)/?$',
permit_requests_api_views.UpdatePermitRequestStatusView.as_view(),
name='permitrequest_api_update',
),
url(
r'^entries/$',
r'^entries/?$',
views.CreateEntryView.as_view(),
name='entry_api_create',
),
url(r'^api-token-auth/', views.ObtainAuthToken.as_view()),
url(r'^api-token-auth/?', views.ObtainAuthToken.as_view()),
]
......@@ -8,6 +8,7 @@ def custom_exception_handler(exc, context):
message = response.data.get('detail')
if not message:
message = response.data.get('non_field_errors')
response.data = {
'status': 'ERROR',
'message': message,
......
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
import { OLD_FORM_SELECTOR, DELETE_CHECKBOX_SELECTOR } from './map-variables';
export default () => {
const $deleteCheckboxes = $(OLD_FORM_SELECTOR).find(DELETE_CHECKBOX_SELECTOR);
$deleteCheckboxes.each((index, checkbox) => {
$(checkbox)
.prop('type', 'checkbox')
.prop('checked', true);
});
};
/* global FileReader */
export default (e, $previewImage) => {
const { files } = e.target;
// If FR is supported and there are files, change the image
if (FileReader && files && files.length) {
const fr = new FileReader();
// Change src attribute to image preview
fr.onload = () => $previewImage.attr('src', fr.result);
fr.readAsDataURL(files[0]);
}
};
const ERROR_CLASS = 'form-error';
const ERROR_SELECTOR = `.${ERROR_CLASS}`;
const SUBMIT_BTN_SELECTOR = '[type="submit"]:not(.btn-back)';
/**
* Remove input error message
* @param {object} $input
*/
function hideInputError($input) {
$input.next(ERROR_SELECTOR).remove();
}
/**
* Add an error message after an input
* @param {object} $input
* @param {string} errorMsg
*/
function showInputError($input, errorMsg) {
const $inputError = $(`<p class="${ERROR_CLASS} font-small text-danger my-2">${errorMsg}</p>`);
$input.after($inputError);
}
/**
* Check if input has any error
* @param {object} $input
*/
function inputHasError($input) {
return $input.next(ERROR_SELECTOR).length;
}
/**
* Check if form has any error
*/
function formHasErrors() {
return $(ERROR_SELECTOR).length;
}
/**
* Add or remove disbled attribute to form submit button
*/
function allowOrDisableFormSubmit() {
if (formHasErrors()) {
$(SUBMIT_BTN_SELECTOR).prop('disabled', true);
} else {
$(SUBMIT_BTN_SELECTOR).prop('disabled', false);
}
}
/**
* Show errors if the value of a input is greater than a number
* or lesser than 0 and hide if not
* @param {object} $input
* @param {number} maxValue
* @param {string} errorMsg
*/
export default function ($input, maxValue, errorMsg) {
const value = parseFloat($input.val()) || 0;
if (value <= maxValue && value >= 0) {
hideInputError($input);
} else if (!inputHasError($input)) {
showInputError($input, errorMsg);
}
allowOrDisableFormSubmit();
}
/* global map */
import {