...
 
Commits (10)
# Generated by Django 2.0.13 on 2019-08-19 19:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('balance', '0003_auto_20190803_1457'),
]
operations = [
migrations.AddField(
model_name='balance',
name='last_modified',
field=models.DateTimeField(auto_now=True),
),
]
......@@ -17,7 +17,7 @@ class Balance(models.Model):
administration = models.CharField(max_length=255)
creator = models.ForeignKey(PennyUser, on_delete=models.SET_NULL, blank=True, null=True)
created = models.DateTimeField(auto_now_add=True)
last_modified = models.DateTimeField(auto_now=True)
categories = models.ManyToManyField('BalanceCategory', related_name='balances')
def __str__(self):
......
This diff is collapsed.
......@@ -8,13 +8,14 @@
{% url 'budget:list' as budgets_list %}
{% url 'budget:posts' as budget_posts %}
{% url 'journal:list' as journals_list %}
{% url 'reports:list' as reports_list %}
{% url 'accounts:list' as accounts_list %}
{% url 'invoices:due' as invoices_due %}
{% url 'invoices:customer_invoice_list' as customer_invoices %}
{% url 'people:suppliers_list' as people_suppliers_list %}
{% url 'people:customers_list' as people_customers_list %}
<nav id="sidebar"{% if sidebar_status == "1" %} class="active"{% endif %}>
<nav class="d-print-none" id="sidebar"{% if sidebar_status == "1" %} class="active"{% endif %}>
<div class="sidebar-header">
<h3 id="sidebar-header-text"{% if sidebar_status == "1" %} class="active"{% endif %}><a href="{{ nav_header_link }}"><img alt="Quaestor" src="{% static 'img/qaestor_logo.svg' %}"></a></h3>
......@@ -97,6 +98,14 @@
</li>
</ul>
</li>
<li class="{% active_menu reports_list allow_sub=True %}">
<a href="#reportsSubmenu" data-toggle="collapse" aria-expanded="false" class="dropdown-toggle"><i class="fa fa-file-pdf"></i>{% trans 'Reports' %}</a>
<ul class="collapse list-unstyled" id="reportsSubmenu">
<li>
<a href="{{ reports_list }}"><i class="fa fa-th-list"></i>{% trans 'Reports list' %}</a>
</li>
</ul>
</li>
<li class="{% active_menu "/people" allow_sub=True %}">
<a href="#peopleSubmenu" data-toggle="collapse" aria-expanded="false" class="dropdown-toggle"><i class="fa fa-users"></i>{% trans 'People' %}</a>
<ul class="collapse list-unstyled" id="peopleSubmenu">
......
# https://docs.celeryproject.org/en/latest/django/first-steps-with-django.html
from __future__ import absolute_import, unicode_literals
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app
__all__ = ('celery_app',)
# https://docs.celeryproject.org/en/latest/django/first-steps-with-django.html
from __future__ import absolute_import, unicode_literals
import os
from celery import Celery
# set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'quaestor.settings')
app = Celery('quaestor')
# Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys
# should have a `CELERY_` prefix.
app.config_from_object('django.conf:settings', namespace='CELERY')
# Load task modules from all registered Django app configs.
app.autodiscover_tasks()
@app.task(bind=True)
def debug_task(self):
print('Request: {0!r}'.format(self.request))
......@@ -51,9 +51,12 @@ INSTALLED_APPS = [
'invoices.apps.InvoicesConfig',
'journal.apps.JournalConfig',
'people.apps.PeopleConfig',
'reports.apps.ReportsConfig',
# Optional apps
'apps.drink_sheet.apps.DrinkSheetConfig',
'django_celery_results',
]
MIDDLEWARE = [
......@@ -204,6 +207,19 @@ MEDIA_URL = "/media/"
# Prevent this site from being framed in other pages
X_FRAME_OPTIONS = 'DENY'
# Celery task scheduler settings
CELERY_ALWAYS_EAGER = True # Always execute tasks in the foreground (blocking)
CELERY_EAGER_PROPAGATES_EXCEPTIONS = True # If ALWAYS_EAGER, show the exceptions in the foreground
CELERY_SEND_TASK_ERROR_EMAILS = True # Errors occurring during task execution will be sent to ADMINS by email
CELERY_TASK_SERIALIZER = 'pickle' # How to serialize the tasks
CELERY_RESULT_BACKEND = 'django-db' # Where to store the task results
CELERY_RESULT_SERIALIZER = 'pickle' # How to serialize the task results
CELERY_ACCEPT_CONTENT = ['pickle'] # A whitelist of content-types/serializers to allow
CELERY_IMPORTS = (
'reports.tasks',
)
# Import local user settings
try:
from quaestor.local import * # noqa
......
......@@ -30,6 +30,7 @@ urlpatterns = [
path('budget/', include('budget.urls')),
path('invoices/', include('invoices.urls')),
path('journal/', include('journal.urls')),
path('reports/', include('reports.urls')),
path('people/', include('people.urls')),
# Include other apps
......
# from django.contrib import admin
# Register your models here.
from django.apps import AppConfig
class ReportsConfig(AppConfig):
name = 'reports'
from django import forms
from reports.models import Report, OverleafConnection, ReportSection
class ReportForm(forms.ModelForm):
class Meta:
model = Report
fields = ('title', 'introduction', )
class ReportSectionForm(forms.ModelForm):
class Meta:
model = ReportSection
fields = ('order', 'title', 'introduction', 'simple', 'balance', 'budget', 'budget_type', 'type', )
def __init__(self, balance_qs, budget_qs, *args, **kwargs):
super(ReportSectionForm, self).__init__(*args, **kwargs)
self.fields['balance'].queryset = balance_qs
self.fields['budget'].queryset = budget_qs
def clean(self):
form_data = self.cleaned_data
if form_data['type'] == 'balance' and not form_data['balance']:
self._errors["balance"] = ["Please choose a balance to use"]
if form_data['type'] == 'budget' and (not form_data['budget'] or not form_data['budget_type']):
self._errors["budget"] = ["Please choose a budget to use"]
return form_data
class OverleafForm(forms.ModelForm):
password = forms.CharField(label="Overleaf Password", max_length=64, widget=forms.PasswordInput)
class Meta:
model = OverleafConnection
fields = ('url', 'email', )
from django.template.loader import render_to_string
from os import path, mkdir
from reports.latex_budget import renderBudget
from reports.latex_balance import renderBalance
# Helper functions which create a render dict consisting of files mapped to a dict, which can then be rendered in LaTeX.
# The reason it not directly creates a LaTeX rendered file is to add secondary information (like depth) later on.
def renderReport(report):
files = {}
# Main report file, shows title page / table of contents and imports quaestor.tex
files['main.tex'] = {
'template': 'main.tex',
'data': {
'author': report.creator.get_full_name(),
'title': report.title
}
}
files['quaestor.tex'] = {
'template': 'quaestor.tex',
'data': {
'sections': report.sections.all(),
'title': report.title
}
}
for section in report.sections.all():
files[str(section)] = renderSection(section)
return files
def renderSection(report_section):
if report_section.type == 'balance':
return renderBalance(report_section)
elif report_section.type == 'budget':
return renderBudget(report_section)
return {}
def render(files, path="", depth=0):
"""Renders a dict of files to the actual LaTeX files, while adding context like depth"""
result = {}
for filepath, file in files.items():
# Check if folder or file
if filepath.endswith('.tex'):
if 'template' in file:
result[filepath] = render_to_string('latex/' + file['template'],
{**file['data'], **{'depth': depth, 'path': path}})
continue
result[filepath] = render(file, path=path + filepath + '/', depth=depth + 1)
return result
def writeFiles(files, folder):
for filepath, file in files.items():
# Check if folder
if(type(file) == dict):
mkdir(path.join(folder, filepath) + '/', mode=0o700)
writeFiles(file, path.join(folder, filepath) + '/')
continue
with open(path.join(folder, filepath), 'w') as writer:
writer.write(file)
def renderBalance(section):
files = renderMainBalance(section.balance)
files['main.tex'] = {
'template': 'balance/main.tex',
'data': {
'title': section.title,
'introduction': section.introduction,
}
}
files['assets.tex'] = renderBreakdown(section.balance, type='assets')['breakdown.tex']
files['liabilities.tex'] = renderBreakdown(section.balance, type='liabilities')['breakdown.tex']
return files
def renderMainBalance(balance):
files = {}
assets = []
for category in balance.asset_categories:
assets.append("\\textbf{{{}}} & &".format(category.name))
for item in category.posts.all():
assets.append("{:02d} {} & \\texttt{{{:,.2f}}} &".format(
item.number, item.name, balance.result if item.is_result else item.totals(balance)))
if category.posts.all():
assets.append("& &")
for subcat in category.children.all():
assets.append("\\textbf{{\\textit{{{}}}}} & &".format(subcat.name))
for item in subcat.posts.all():
assets.append("{:02d} {} & \\texttt{{{:,.2f}}} &".format(
item.number, item.name, balance.result if item.is_result else item.totals(balance)))
assets.append("& &")
assets.append("\\textbf{{Subtotal}} & \\textbf{{\\texttt{{{:,.2f}}}}} &".format(category.totals(balance)))
assets.append("& &")
liabilities = []
for category in balance.liability_categories:
liabilities.append("\\textbf{{{}}} & \\\\".format(category.name))
for item in category.posts.all():
liabilities.append("{:02d} {} & \\texttt{{{:,.2f}}} \\\\".format(
item.number, item.name, balance.result if item.is_result else item.totals(balance)))
if category.posts.all():
liabilities.append("& \\\\")
for subcat in category.children.all():
liabilities.append("\\textbf{{\\textit{{{}}}}} & \\\\".format(subcat.name))
for item in subcat.posts.all():
liabilities.append("{:02d} {} & \\texttt{{{:,.2f}}} \\\\".format(
item.number, item.name, balance.result if item.is_result else item.totals(balance)))
liabilities.append("& \\\\")
liabilities.append("\\textbf{{Subtotal}} & \\textbf{{\\texttt{{{:,.2f}}}}} \\\\".format(
category.totals(balance)))
liabilities.append("& \\\\")
size = max(len(liabilities), len(assets))
assets += ["& \\\\"] * (size - len(assets))
liabilities += ["& \\\\"] * (size - len(liabilities))
assets.append("\\textbf{{Total}} & \\textbf{{\\texttt{{{:,.2f}}}}} &".format(balance.assets_total))
liabilities.append("\\textbf{{Total}} & \\textbf{{\\texttt{{{:,.2f}}}}} \\\\".format(balance.liabilities_total))
files['balance.tex'] = {
'template': 'balance/balance.tex',
'data': {'rows': list(zip(assets, liabilities))}
}
return files
def renderBreakdown(balance, type='assets'):
files = {}
posts_rendered = []
posts = balance.get_assets_breakdown_posts if type == 'assets' else balance.get_liabilities_breakdown_posts
for post in posts:
post_rendered = []
post_rendered.append("\\textbf{{{:02d} {}}} & ".format(post.number, post.name))
# For entry
for entry in post.get_breakdown_entries(balance):
if entry.italic:
post_rendered.append("\textit{{{} & \\texttt{{{:,.2f}}}}} ".format(
entry.description,
entry.total,
))
else:
post_rendered.append("{} & \\texttt{{{:,.2f}}}".format(
entry.description,
entry.total,
))
pass
# Total
post_rendered.append("\\textbf{{Total}} & \\textbf{{\\texttt{{{:,.2f}}}}} ".format(
post.get_breakdown_total(balance)
))
post_rendered.append(" & ")
posts_rendered.append(post_rendered)
# Split posts into two rows
half_length = sum(map(lambda post: len(post), posts_rendered)) / 2
left = []
right = []
for post in posts_rendered:
if(len(left) < half_length):
left.extend(post)
else:
right.extend(post)
size = max(len(left), len(right))
left += [" & "] * (size - len(left))
right += [" & "] * (size - len(right))
files['breakdown.tex'] = {
'template': 'balance/assets.tex' if type == 'assets' else 'balance/liabilities.tex',
'data': {'rows': list(zip(left, right))}
}
return files
def renderBudget(section):
"""Renders an entire budget, and if not simple, all it's sub budgets"""
budget = section.budget
# simple = section.simple
files = renderBudgetPart(budget, title=False)
parts = []
for entry in budget.entries.all():
if entry.child_budget:
name = '{:02d}-{}.tex'.format(entry.order, entry.description.replace(" ", "-"))
parts.append(name)
files[name] = renderBudgetPart(entry.child_budget)['budget.tex']
files['main.tex'] = {
'template': 'budget/main.tex',
'data': {
'title': section.title,
'introduction': section.introduction,
'parts': parts
}
}
return files
def renderBudgetPart(budget_part, title=True, type='year'):
if type == 'year':
return renderBudgetPartYear(budget_part, title=title)
elif type == 'prognosis':
return renderBudgetPartPrognosis(budget_part, title=title)
else:
return renderBudgetPartBudget(budget_part, title=title)
def renderBudgetPartYear(budget_part, title=True):
files = {}
context = {}
context['current_year'] = str(budget_part.fiscal_year)[2:]
context['next_year'] = str(budget_part.fiscal_year + 1)[2:]
if title:
context['title'] = budget_part.description
context['rows'] = []
for entry in budget_part.entries.all():
context['rows'].append(
"{:02d} & {} & \\texttt{{{}}} & \\texttt{{{}}} & \\texttt{{{}}} & \\texttt{{{}}} \\\\"
.format(
entry.order,
entry.description,
"{:,.2f}".format(entry.costs_budgeted) if entry.costs_budgeted else "-",
"{:,.2f}".format(entry.profits_budgeted) if entry.profits_budgeted else "-",
"{:,.2f}".format(entry.costs_results_expected) if entry.costs_results_expected else "-",
"{:,.2f}".format(entry.profits_results_expected) if entry.profits_results_expected else "-"
))
context['totals'] = ("&\\textbf{{Total}}& \\texttt{{{:,.2f}}} & \\texttt{{{:,.2f}}} "
+ "& \\texttt{{{:,.2f}}} & \\texttt{{{:,.2f}}} \\\\") \
.format( # noqa E113
budget_part.total_costs_budget,
budget_part.total_profits_budget,
budget_part.total_costs_results_actual,
budget_part.total_profits_results_actual
)
files['budget.tex'] = {
'template': 'budget/year.tex',
'data': context
}
return files
def renderBudgetPartBudget(budget_part, title=True):
files = {}
context = {}
context['previous_year'] = str(budget_part.fiscal_year - 1)[2:]
context['current_year'] = str(budget_part.fiscal_year)[2:]
context['next_year'] = str(budget_part.fiscal_year + 1)[2:]
if title:
context['title'] = budget_part.description
context['rows'] = []
for entry in budget_part.entries.all():
context['rows'].append(
"{:02d} & {} & \\texttt{{{}}} & \\texttt{{{}}} & \\texttt{{{}}} & \\texttt{{{}}} \\\\"
.format(
entry.order,
entry.description,
"{:,.2f}".format(entry.costs_results_previous) if entry.costs_results_previous else "-",
"{:,.2f}".format(entry.profits_results_previous) if entry.profits_results_previous else "-",
"{:,.2f}".format(entry.costs_budgeted) if entry.costs_budgeted else "-",
"{:,.2f}".format(entry.profits_budgeted) if entry.profits_budgeted else "-"
))
context['totals'] = ("&\\textbf{{Total}}& \\texttt{{{:,.2f}}} & \\texttt{{{:,.2f}}} "
+ "& \\texttt{{{:,.2f}}} & \\texttt{{{:,.2f}}} \\\\") \
.format( # noqa E113
budget_part.total_costs_results_previous,
budget_part.total_profits_results_previous,
budget_part.total_costs_budget,
budget_part.total_profits_budget
)
files['budget.tex'] = {
'template': 'budget/budget.tex',
'data': context
}
return files
def renderBudgetPartPrognosis(budget_part, title=True):
files = {}
context = {}
context['current_year'] = str(budget_part.fiscal_year)[2:]
context['next_year'] = str(budget_part.fiscal_year + 1)[2:]
context['quarter'] = str(budget_part.quarter)
if title:
context['title'] = budget_part.description
context['rows'] = []
for entry in budget_part.entries.all():
context['rows'].append(
"{:02d} & {} & \\texttt{{{}}} & \\texttt{{{}}} & \\texttt{{{}}} & \\texttt{{{}}} & "
"\\texttt{{{}}} & \\texttt{{{}}} & \\texttt{{{}}} & \\texttt{{{}}} \\\\"
.format(
entry.order,
entry.description,
"{:,.2f}".format(entry.costs_budgeted) if entry.costs_budgeted else "-",
"{:,.2f}".format(entry.profits_budgeted) if entry.profits_budgeted else "-",
"{:,.2f}".format(entry.costs_results_quarter) if entry.costs_results_quarter else "-",
"{:,.2f}".format(entry.profits_results_quarter) if entry.profits_results_quarter else "-",
"{:,.2f}".format(entry.costs_prognosed) if entry.costs_prognosed else "-",
"{:,.2f}".format(entry.profits_prognosed) if entry.profits_prognosed else "-",
"{:,.2f}".format(entry.costs_results_expected) if entry.costs_results_expected else "-",
"{:,.2f}".format(entry.profits_results_expected) if entry.profits_results_expected else "-"
))
context['totals'] = ("&\\textbf{{Total}}& \\texttt{{{:,.2f}}} & \\texttt{{{:,.2f}}} "
"& \\texttt{{{:,.2f}}} & \\texttt{{{:,.2f}}} & \\texttt{{{:,.2f}}} & \\texttt{{{:,.2f}}} & "
"& \\texttt{{{:,.2f}}} & \\texttt{{{:,.2f}}} \\\\") \
.format( # noqa E113
budget_part.total_costs_budget,
budget_part.total_profits_budget,
budget_part.total_costs_results_quarter,
budget_part.total_profits_results_quarter,
budget_part.total_costs_prognosis,
budget_part.total_profits_prognosis,
budget_part.total_costs_results_expected,
budget_part.total_profits_results_expected
)
files['budget.tex'] = {
'template': 'budget/prognosis.tex',
'data': context
}
return files
# Generated by Django 2.0.13 on 2019-08-20 13:18
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import reports.models
class Migration(migrations.Migration):
initial = True
dependencies = [
('balance', '0004_balance_last_modified'),
('budget', '0004_auto_20190731_1856'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Report',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255)),
('introduction', models.TextField(blank=True, verbose_name='Introduction')),
('administration', models.CharField(editable=False, max_length=255)),
('created', models.DateTimeField(auto_now_add=True)),
('last_modified', models.DateTimeField(auto_now=True)),
],
),
migrations.CreateModel(
name='ReportSection',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('order', models.IntegerField(verbose_name='Order')),
('title', models.CharField(max_length=255, verbose_name='Title')),
('introduction', models.TextField(blank=True, verbose_name='Introduction')),
('budget_type', models.CharField(blank=True, choices=[('quarter', 'Quarterly'), ('year', 'Yearly'), ('budget', 'Budget')], max_length=10)),
('type', models.CharField(choices=[('balance', 'Balance'), ('budget', 'Budget')], max_length=10)),
('simple', models.BooleanField(verbose_name='Simple?')),
('balance', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='balance.Balance')),
('budget', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='budget.Budget')),
],
options={
'ordering': ['order'],
},
),
migrations.CreateModel(
name='OverleafConnection',
fields=[
('url', models.URLField(validators=[django.core.validators.RegexValidator(code='invalid_url', message='Please provide a valid Overleaf URL', regex='(?:^)https:\\/\\/git.overleaf.com\\/(?:)[a-zA-Z0-9]+$')], verbose_name='Git URL')),
('email', models.EmailField(max_length=254, verbose_name='Overleaf Email address')),
('report', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='overleaf', serialize=False, to='reports.Report')),
('state', models.CharField(choices=[(reports.models.OverleafConnectionState('Synced'), 'Synced'), (reports.models.OverleafConnectionState('Syncing'), 'Syncing'), (reports.models.OverleafConnectionState('Broken'), 'Broken')], max_length=10)),
('last_sync', models.DateTimeField(null=True)),
('hashed_password', models.CharField(max_length=128)),
('sync_id', models.IntegerField(blank=True, null=True)),
],
),
migrations.AddField(
model_name='reportsection',
name='report',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sections', to='reports.Report'),
),
migrations.AddField(
model_name='report',
name='creator',
field=models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
),
]
from django.db import models
from django.urls import reverse
from django.core.validators import RegexValidator
from penny.models import PennyUser
from enum import Enum
from balance.models import Balance
from budget.models import Budget
class Report(models.Model):
title = models.CharField(max_length=255)
introduction = models.TextField(verbose_name='Introduction', blank=True)
administration = models.CharField(max_length=255, editable=False)
creator = models.ForeignKey(PennyUser, on_delete=models.SET_NULL, editable=False, blank=True, null=True)
created = models.DateTimeField(auto_now_add=True)
last_modified = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("reports:detail", kwargs={"pk": self.pk})
class ReportSection(models.Model):
report = models.ForeignKey(Report, on_delete=models.CASCADE, related_name='sections')
order = models.IntegerField(verbose_name='Order')
title = models.CharField(max_length=255, verbose_name='Title')
introduction = models.TextField(verbose_name='Introduction', blank=True)
balance = models.ForeignKey(Balance, on_delete=models.CASCADE, null=True, blank=True)
budget = models.ForeignKey(Budget, on_delete=models.CASCADE, null=True, blank=True)
BUDGET_TYPES = [('quarter', 'Quarterly'), ('year', 'Yearly'), ('budget', 'Budget')]
budget_type = models.CharField(max_length=10, choices=BUDGET_TYPES, blank=True)
ALLOWED_TYPES = [('balance', 'Balance'), ('budget', 'Budget')]
type = models.CharField(max_length=10, choices=ALLOWED_TYPES)
simple = models.BooleanField(verbose_name='Simple?')
class Meta:
ordering = ['order']
def __str__(self):
return "{}-{}".format(self.type, self.inner_object.id)
@property
def inner_object(self):
return self.balance if self.type == 'balance' else self.budget
@property
def last_modified(self):
return self.inner_object.last_modified
@property
def creator(self):
return self.inner_object.creator
def get_absolute_url(self):
return self.inner_object.get_absolute_url()
# TODO be able to keep track of report changes
class OverleafConnectionState(Enum):
# OUT_OF_SYNC = "Out of sync"
SYNCED = "Synced"
SYNCING = "Syncing"
BROKEN = "Broken"
class OverleafConnection(models.Model):
url = models.URLField(verbose_name='Git URL', validators=[
RegexValidator(
regex=r"(?:^)https:\/\/git.overleaf.com\/(?:)[a-zA-Z0-9]+$",
message='Please provide a valid Overleaf URL',
code="invalid_url"
),
])
email = models.EmailField(verbose_name='Overleaf Email address')
report = models.OneToOneField(Report, on_delete=models.CASCADE, primary_key=True, related_name='overleaf')
state = models.CharField(max_length=10, choices=[(state, state.value) for state in OverleafConnectionState])
last_sync = models.DateTimeField(null=True)
hashed_password = models.CharField(max_length=128)
sync_id = models.IntegerField(null=True, blank=True)
def __str__(self):
return "overleaf-connection-{}".format(self.report)
from celery.decorators import task
from celery.utils.log import get_task_logger
from reports.latex import renderReport, render, writeFiles
from git import Repo
from git.exc import GitCommandError
from urllib.parse import quote
import datetime
from reports.models import OverleafConnectionState, OverleafConnection
import os
import shutil
logger = get_task_logger(__name__)
@task(name="sync_report_to_overleaf", bind=True)
def sync_overleaf(self, overleaf_connection_id, password):
"""sync report to overleaf through Git integration"""
overleaf_connection = OverleafConnection.objects.get(pk=overleaf_connection_id)
GIT_FOLDER = '/tmp/quaestor-overleaf-{}'.format(overleaf_connection.pk)
# Check if folder already exists and delete in that case
if os.path.exists(GIT_FOLDER) and os.path.isdir(GIT_FOLDER):
shutil.rmtree(GIT_FOLDER)
try:
os.mkdir(GIT_FOLDER, mode=0o700) # Only give read/write to user running current process
except OSError as e:
print(e)
self.retry(e)
# Render project to LaTeX
files = render(renderReport(overleaf_connection.report), path='quaestor/')
# Pull project from overleaf
try:
repo = Repo.clone_from(
'https://{}:{}@{}'.format(
quote(overleaf_connection.email),
quote(password),
overleaf_connection.url[8:]
),
GIT_FOLDER
)
except GitCommandError:
# Most likely URL or login information error
overleaf_connection.state = OverleafConnectionState.BROKEN
return overleaf_connection.save()
# (Over)write project files
QUAESTOR_FOLDER = os.path.join(GIT_FOLDER, 'quaestor/')
if os.path.exists(QUAESTOR_FOLDER) and os.path.isdir(QUAESTOR_FOLDER):
shutil.rmtree(QUAESTOR_FOLDER)
os.mkdir(QUAESTOR_FOLDER, mode=0o700)
writeFiles(files, QUAESTOR_FOLDER)
# Copy IA images
os.mkdir(os.path.join(QUAESTOR_FOLDER, 'images/'), mode=0o700)
shutil.copy2('reports/templates/latex/images/inter-actief-logo-onder.pdf',
os.path.join(QUAESTOR_FOLDER, 'images/'))
shutil.copy2('reports/templates/latex/images/inter-actief-logo-tekst-rechts.pdf',
os.path.join(QUAESTOR_FOLDER, 'images/'))
# Commit and push to project
try:
repo.git.add('.')
repo.git.commit('-m Quaestor update at {}'.format(datetime.datetime.now()), author=overleaf_connection.email)
except GitCommandError:
pass
try:
repo.git.push()
except GitCommandError:
# Concurency error
self.retry()
finally:
# Cleanup
shutil.rmtree(GIT_FOLDER)
overleaf_connection.state = OverleafConnectionState.SYNCED
return overleaf_connection.save()
\begin{adjustbox}{max width=\textwidth}
\rowcolors{1}{}{newblue}
\def \arraystretch{1.3}
\begin{tabular}{@{}lr@{\hskip 20pt}lr@{}}
{% for line in rows %}{{ line.0|safe }} & {{ line.1|safe }} \\
{% endfor %}
\end{tabular}
\end{adjustbox}
\ No newline at end of file
\begin{adjustbox}{max width=\textwidth}
\rowcolors{1}{}{newblue}
\def \arraystretch{1.3}
\begin{tabular}{@{}lr||lr@{}}
\multicolumn{2}{c}{\textbf{Assets}} & \multicolumn{2}{c}{\textbf{Liabilities}} \\
{% for line in rows %}{{ line.0|safe }} {{ line.1|safe }}
{% endfor %}
\end{tabular}
\end{adjustbox}
\ No newline at end of file
\begin{adjustbox}{max width=\textwidth}
\rowcolors{1}{}{newblue}
\def \arraystretch{1.3}
\begin{tabular}{@{}lr@{\hskip 20pt}lr@{}}
{% for line in rows %} {{ line.0|safe }} & {{ line.1|safe }} \\
{% endfor %}
\end{tabular}
\end{adjustbox}
\ No newline at end of file
\subsection{% templatetag openbrace %}{{ title }}}
\input{% templatetag openbrace %}{{path}}balance.tex} \\
\input{% templatetag openbrace %}{{path}}assets.tex} \\
\input{% templatetag openbrace %}{{path}}liabilities.tex} \\
{% if title %}\subsubsection{% templatetag openbrace %}{{ title }}}{% endif %}
{% verbatim %}
\begin{adjustbox}{max width=\textwidth}
\def\arraystretch{1.5}
\rowcolors{1}{}{newblue}
\begin{tabular}{rl@{\hskip 10pt}@{\hskip 10pt}rr@{\hskip 10pt}@{\hskip 10pt}rr}
\toprule \hiderowcolors
&&\multicolumn{2}{c@{\hskip 10pt}@{\hskip 10pt}}{\textbf{Result {% endverbatim %}{{ previous_year }}/{{ current_year }}{% verbatim %}}}&\multicolumn{2}{c}{\textbf{Budget {% endverbatim %}{{ current_year }}/{{ next_year }}{% verbatim %}}} \\
& \textbf{Description} & \multicolumn{1}{c}{\textbf{Debit}} & \multicolumn{1}{c}{\textbf{Credit}} & \multicolumn{1}{c}{\textbf{Debit}} & \multicolumn{1}{c}{\textbf{Credit}} \\ \midrule \showrowcolors
{% endverbatim %}{% for row in rows %}{{ row|safe }}
{% endfor %}{% verbatim %}
\midrule \hiderowcolors
{% endverbatim %}{{ totals|safe }}{% verbatim %}
\bottomrule
\end{tabular}
\end{adjustbox}
{% endverbatim %}
\ No newline at end of file
\subsection{% templatetag openbrace %}{{ title }}}
\input{% templatetag openbrace %}{{path}}budget.tex}
{% for part in parts %}\input{% templatetag openbrace %}{{path}}{{part}}}
{% endfor %}
\ No newline at end of file
{% verbatim %}
\begin{adjustbox}{max width=\textwidth}
\def\arraystretch{1.5}
\rowcolors{1}{}{newblue}
\begin{tabular}{rl@{\hskip 10pt}@{\hskip 10pt}rr@{\hskip 10pt}@{\hskip 10pt}rr@{\hskip 10pt}@{\hskip 10pt}rr@{\hskip 10pt}@{\hskip 10pt}rr}
\toprule \hiderowcolors
&&\multicolumn{2}{c@{\hskip 10pt}@{\hskip 10pt}}{\textbf{Budget {% endverbatim %}{{ current_year }}/{{ next_year }}{% verbatim %}}} &\multicolumn{2}{c}{\textbf{Result until Q{% endverbatim %}{{ quarter }}{% verbatim %}}} &\multicolumn{2}{c}{\textbf{Prognosis until Q4}} & \multicolumn{2}{c}{\textbf{Expected result {% endverbatim %}{{ current_year }}/{{ next_year }}{% verbatim %}}} \\
& \textbf{Description} & \multicolumn{1}{c}{\textbf{Debit}} & \multicolumn{1}{c}{\textbf{Credit}} & \multicolumn{1}{c}{\textbf{Debit}} & \multicolumn{1}{c}{\textbf{Credit}} & \multicolumn{1}{c}{\textbf{Debit}} & \multicolumn{1}{c}{\textbf{Credit}} & \multicolumn{1}{c}{\textbf{Debit}} & \multicolumn{1}{c}{\textbf{Credit}} \\ \midrule \showrowcolors
{% endverbatim %}{% for row in rows %}{{ row|safe }}
{% endfor %}{% verbatim %}
\midrule \hiderowcolors
{% endverbatim %}{{ totals|safe }}{% verbatim %}
\bottomrule
\end{tabular}
\end{adjustbox}
{% endverbatim %}
\ No newline at end of file
{% verbatim %}
\begin{adjustbox}{max width=\textwidth}
\def\arraystretch{1.5}
\rowcolors{1}{}{newblue}
\begin{tabular}{rl@{\hskip 10pt}@{\hskip 10pt}rr@{\hskip 10pt}@{\hskip 10pt}rr}
\toprule \hiderowcolors
&&\multicolumn{2}{c@{\hskip 10pt}@{\hskip 10pt}}{\textbf{Budget {% endverbatim %}{{ current_year }}/{{ next_year }}{% verbatim %}}}&\multicolumn{2}{c}{\textbf{Result {% endverbatim %}{{ current_year }}/{{ next_year }}{% verbatim %}}} \\
& \textbf{Description} & \multicolumn{1}{c}{\textbf{Debit}} & \multicolumn{1}{c}{\textbf{Credit}} & \multicolumn{1}{c}{\textbf{Debit}} & \multicolumn{1}{c}{\textbf{Credit}} \\ \midrule \showrowcolors
{% endverbatim %}{% for row in rows %}{{ row|safe }}
{% endfor %}{% verbatim %}
\midrule \hiderowcolors
{% endverbatim %}{{ totals|safe }}{% verbatim %}
\bottomrule
\end{tabular}
\end{adjustbox}
{% endverbatim %}
\ No newline at end of file
{{ comment }}
\ No newline at end of file
{% autoescape off %}
% *** WARNING ***
% THIS DOCUMENT IS MANAGED BY QUAESTOR.
% ANY CHANGES INTRODUCED MANUALLY WILL BE LOST!
% RESISTANCE IS FUTILE!
\documentclass[english,a4paper,12pt,twoside]{article}
\usepackage[utf8]{inputenc}
\usepackage{adjustbox}
\usepackage{amsmath}
\usepackage{amsfonts}
\usepackage{amssymb}
\usepackage{array}
\usepackage[english]{babel}
\usepackage{booktabs}
\usepackage{enumitem}
\usepackage{changepage}
\usepackage[T1]{fontenc}
\usepackage{graphicx}
\usepackage{hhline}
\usepackage{titlesec}
\usepackage[official]{eurosym}
\usepackage{lmodern}
\usepackage[table]{xcolor}
\usepackage{colortbl}
\usepackage[margin=1in,showframe=false]{geometry}
\usepackage{colortbl}
\usepackage[modulo]{lineno}
\usepackage{multicol}
\usepackage[hidelinks]{hyperref}
\usepackage{pbox}
\usepackage{tabularx}
\usepackage{pdfpages}
\usepackage{fancyhdr}
\raggedbottom
\pagestyle{fancy}
\setcounter{tocdepth}{3}
\newcommand{\ia}{Inter-\textit{Actief}}
\newcommand{\loneparagraph}[1]{\paragraph{#1}\mbox{}\\ \mbox{}\\}
\newcommand{\stukno}{Document number GMMXXX.XX}
\author{% endautoescape %}{% templatetag openbrace %}{{ author }}{% autoescape off %}\\
I.C.T.S.V. \ia}
\title{% endautoescape %}{% templatetag openbrace %}{{ title }}{% autoescape off %}}
\makeatletter
\fancyhf{}
\renewcommand{\headrulewidth}{0pt}
\setlength{\headheight}{25pt}
\fancyhead[LO,RE]{\raisebox{-0.35cm}{\includegraphics[height=0.85cm]{quaestor/images/inter-actief-logo-tekst-rechts.pdf}}}
\fancyhead[RO]{{\scriptsize
\begin{tabular}{r|l}
\textbf{\stukno} & \textbf{\thepage} \\
\textbf{\@title} & \\
\end{tabular}}}
\fancyhead[LE]{{\scriptsize
\begin{tabular}{r|l}
\textbf{\thepage} & \textbf{\stukno} \\
& \textbf{\@title} \\
\end{tabular}}}
\fancyfoot[C]{}
\titleformat{\chapter}[display]
{\normalfont\bfseries}{}{0pt}{\Large}
\definecolor{newblue}{HTML}{EDF2FC}
\begin{document}
\clearpage
\makeatletter
\begin{titlepage}
\begin{center}
\includegraphics[width=0.8\linewidth]{quaestor/images/inter-actief-logo-onder.pdf}
{\huge \bfseries \@title \\}
{\LARGE \@author \\}
\end{center}
\end{titlepage}
\makeatother
\thispagestyle{empty}
\newpage
\thispagestyle{fancy}
\tableofcontents
\newpage
\linenumbers
\newpage
\input{quaestor/quaestor.tex}
\end{document}
{% endautoescape %}
\ No newline at end of file
% *** WARNING ***
% THIS DOCUMENT IS MANAGED BY QUAESTOR.
% ANY CHANGES INTRODUCED MANUALLY WILL BE LOST!
% RESISTANCE IS FUTILE!
% title
\section{% templatetag openbrace %}{{ title }}}
{% for section in sections %}\input{quaestor/{{section}}/main.tex}
{% endfor %}
\ No newline at end of file
This diff is collapsed.
{% include 'reports/full/budget_part.html' with budget=section.budget section=section %}
<p class="row-padding"></p>
{% for budget_entry in section.budget.entries.all %}
{% with budget_entry.child_budget as child_budget %}
{% if child_budget %}
<div class="row">
<div class="col">
<h3 id="budget_detail_table_{{ child_budget.pk }}">{{ child_budget.description }}</h3>
{% include 'reports/full/budget_part.html' with budget=child_budget section=section %}
<p class="row-padding"></p>
{% comment %} <h4>{% trans 'Comments' %}</h4>
{% include 'budget/budget_detail_comments.html' with budget=child_budget %} {% endcomment %}
</div>
</div>
{% endif %}
{% endwith %}
{% endfor %}
\ No newline at end of file
{% load i18n %}
<table class="table table-striped table-hover table-sm debit-credit-table">
<thead class="thead-dark">
<tr>
<th colspan="2"></th>
<th>&nbsp;</th>
<th colspan="2">{% trans 'Result' %} {{ budget.fiscal_year|add:"-1" }}/{{ budget.fiscal_year }}</th>
<th>&nbsp;</th>
<th colspan="2">{% trans 'Budget' %} {{ budget.fiscal_year }}/{{ budget.fiscal_year|add:"1" }}</th>
<th>&nbsp;</th>
</tr>
<tr>
<th>#</th>
<th>{% trans 'Description' %}</th>
<th>&nbsp;</th>
<th>{% trans 'Debit' %}</th>
<th>{% trans 'Credit' %}</th>
<th>&nbsp;</th>
<th>{% trans 'Debit' %}</th>
<th>{% trans 'Credit' %}</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
{% for budget_entry in budget.entries.all %}{% with budget_entry.child_post as post %}{% with budget_entry.child_budget as child_budget %}
<tr>
<td class="order-column">{{ budget_entry.order }}</td>
<td class="description-column">
{% if post %}
{{ post.description }}
{% elif child_budget %}
<a href="#budget_detail_table_{{ child_budget.pk }}"><i class="fa fa-briefcase"></i> {{ child_budget.short_description }}</a>
{% endif %}
</td>
<td>&nbsp;</td>
{# Results last year #}
<td class="debit-column">
{% if post.costs_account %}
<a title="This link will change the active fiscal year" href="{% url 'switch_fiscal_year' previous_year %}?next={% url 'accounts:detail' post.costs_account %}">
<i class="fa fa-exchange-alt"></i>
{% endif %}
{{ budget_entry.costs_results_previous|default_if_none:0|floatformat:"2" }}
{% if post.costs_account %}</a>{% endif %}
</td>
<td class="credit-column">
{% if post.profits_account %}
<a title="This link will change the active fiscal year" href="{% url 'switch_fiscal_year' previous_year %}?next={% url 'accounts:detail' post.profits_account %}">
<i class="fa fa-exchange-alt"></i>
{% endif %}
{{ budget_entry.profits_results_previous|default_if_none:0|floatformat:"2" }}
{% if post.profits_account %}</a>{% endif %}
</td>
<td>&nbsp;</td>
{# Budget current year #}
<td class="debit-column">{{ budget_entry.costs_budgeted|default_if_none:0|floatformat:"2" }}</td>
<td class="credit-column">{{ budget_entry.profits_budgeted|default_if_none:0|floatformat:"2" }}</td>
<td>&nbsp;</td>
</tr>
{% endwith %}{% endwith %}{% empty %}
<tr>
<td colspan="{% if budget.with_prognosis %}14{% else %}22{% endif %}">{% trans 'No posts in this budget' %}</td>
</tr>
{% endfor %}
{% if budget.show_result %}
<tr>
<td class="order-column"></td>
<td class="description-column">
{% trans 'Result' %}
</td>
<td>&nbsp;</td>
{# Results last year #}
<td class="debit-column">
{% if budget.total_difference_results_previous > 0 %}
{{ budget.total_difference_results_previous|default_if_none:0|floatformat:"2" }}
{% else %}
0.00
{% endif %}
</td>
<td class="credit-column">
{% if budget.total_difference_results_previous < 0 %}
{{ budget.total_difference_results_previous|default_if_none:0|floatformat:"2"|slice:"1:" }}
{% else %}
0.00
{% endif %}
</td>
{% if show_differences %}<td class="differences-column"></td>{% endif %}
<td>&nbsp;</td>
{# Budget current year #}
<td class="debit-column">
{% if budget.total_difference_budget > 0 %}
{{ budget.total_difference_budget|default_if_none:0|floatformat:"2" }}
{% else %}
0.00
{% endif %}
</td>
<td class="credit-column">
{% if budget.total_difference_budget < 0 %}
{{ budget.total_difference_budget|default_if_none:0|floatformat:"2"|slice:"1:" }}
{% else %}
0.00
{% endif %}
</td>
<td>&nbsp;</td>
</tr>
{% endif %}
<tr class="table-dark">
<td></td>
<th scope="row">{% trans 'Totals' %}</th>
<td>&nbsp;</td>
{# Results last year #}
<td class="debit-column">{{ budget.total_costs_results_previous|default_if_none:0|floatformat:"2" }}</td>
<td class="credit-column">{{ budget.total_profits_results_previous|default_if_none:0|floatformat:"2" }}</td>
<td>&nbsp;</td>
{# Budget current year #}
<td class="debit-column">{{ budget.total_costs_budget|default_if_none:0|floatformat:"2" }}</td>
<td class="credit-column">{{ budget.total_profits_budget|default_if_none:0|floatformat:"2" }}</td>
<td>&nbsp;</td>
</tr>
</tbody>
</table>
{% if section.budget_type == 'year' %}
{% include 'reports/full/budget_year.html' with budget=budget %}
{% elif section.budget_type == 'budget' %}
{% include 'reports/full/budget_budget.html' with budget=budget %}
{% else %}
{% include 'reports/full/budget_quarter.html' with budget=budget %}
{% endif %}
\ No newline at end of file
This diff is collapsed.
{% load i18n %}
<table class="table table-striped table-hover table-sm debit-credit-table">
<thead class="thead-dark">
<tr>
<th colspan="2"></th>
<th>&nbsp;</th>
<th colspan="2">{% trans 'Budget' %} {{ budget.fiscal_year }}/{{ budget.fiscal_year|add:"1" }}</th>
<th>&nbsp;</th>
<th colspan="2">{% trans 'Result' %} {{ budget.fiscal_year }}/{{ budget.fiscal_year|add:"1" }}</th>
<th>&nbsp;</th>
</tr>
<tr>
<th>#</th>
<th>{% trans 'Discription' %}</th>
<th>&nbsp;</th>
<th>{% trans 'Debit' %}</th>
<th>{% trans 'Credit' %}</th>
<th>&nbsp;</th>
<th>{% trans 'Debit' %}</th>
<th>{% trans 'Credit' %}</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
{% for budget_entry in budget.entries.all %}{% with budget_entry.child_post as post %}{% with budget_entry.child_budget as child_budget %}
<tr>
<td class="order-column">{{ budget_entry.order }}</td>
<td class="description-column">
{% if post %}
{{ post.description }}
{% elif child_budget %}
<a href="#budget_detail_table_{{ child_budget.pk }}"><i class="fa fa-briefcase"></i> {{ child_budget.short_description }}</a>
{% endif %}
</td>
<td>&nbsp;</td>
{# Budget current year #}
<td class="debit-column">{{ budget_entry.costs_budgeted|default_if_none:0|floatformat:"2" }}</td>
<td class="credit-column">{{ budget_entry.profits_budgeted|default_if_none:0|floatformat:"2" }}</td>
<td>&nbsp;</td>
{# Result current year #}
<td class="debit-column">
{% if post.costs_account %}<a href="{% url 'accounts:detail' post.costs_account %}">{% endif %}
{{ budget_entry.costs_results_actual|default_if_none:0|floatformat:"2" }}
{% if post.costs_account %}</a>{% endif %}
</td>
<td class="credit-column">
{% if post.profits_account %}<a href="{% url 'accounts:detail' post.profits_account %}">{% endif %}
{{ budget_entry.profits_results_actual|default_if_none:0|floatformat:"2" }}
{% if post.profits_account %}</a>{% endif %}
</td>
<td>&nbsp;</td>
</tr>
{% endwith %}{% endwith %}{% empty %}
<tr>
<td colspan="{% if budget.with_prognosis %}14{% else %}22{% endif %}">{% trans 'No posts in this budget.' %}</td>
</tr>
{% endfor %}
{% if budget.show_result %}
<tr>
<td class="order-column"></td>
<td class="description-column">
{% trans 'Result' %}
</td>
<td>&nbsp;</td>
{# Budget current year #}
<td class="debit-column">
{% if budget.total_difference_budget > 0 %}
{{ budget.total_difference_budget|default_if_none:0|floatformat:"2" }}
{% else %}
0.00
{% endif %}
</td>
<td class="credit-column">
{% if budget.total_difference_budget < 0 %}
{{ budget.total_difference_budget|default_if_none:0|floatformat:"2"|slice:"1:" }}
{% else %}
0.00
{% endif %}
</td>
<td>&nbsp;</td>
{# Result current year #}
<td class="debit-column">
{% if budget.total_difference_results_actual > 0 %}
{{ budget.total_difference_results_actual|default_if_none:0|floatformat:"2" }}
{% else %}
0.00
{% endif %}
</td>
<td class="credit-column">
{% if budget.total_difference_results_actual < 0 %}
{{ budget.total_difference_results_actual|default_if_none:0|floatformat:"2"|slice:"1:" }}
{% else %}
0.00
{% endif %}
</td>
<td>&nbsp;</td>
</tr>
{% endif %}
<tr class="table-dark">
<td></td>
<th scope="row">{% trans 'Totals' %}</th>
<td>&nbsp;</td>
{# Budget current year #}
<td class="debit-column">{{ budget.total_costs_budget|default_if_none:0|floatformat:"2" }}</td>
<td class="credit-column">{{ budget.total_profits_budget|default_if_none:0|floatformat:"2" }}</td>
<td>&nbsp;</td>
{# Result current year #}
<td class="debit-column">{{ budget.total_costs_results_actual|default_if_none:0|floatformat:"2" }}</td>
<td class="credit-column">{{ budget.total_profits_results_actual|default_if_none:0|floatformat:"2" }}</td>
<td>&nbsp;</td>
</tr>
</tbody>
</table>
{% extends 'base/base.html' %}
{% load i18n %}
{% load popover %}
{% block page_title %}{% trans 'Reports' %}{% endblock %}
{% block content %}
<h1 class="display-4 d-print-none">{% trans 'Full report' %}</h1>
<p class="d-print-none"><a class="btn btn-secondary" href="{% url 'reports:detail' object.pk %}">{% trans 'Back' %}</a></p>
<div class="row" style="margin-top: 40px;">
<div class="col-sm-12 col-md-12 col-lg-12 col-xl-12" style="max-width: 230mm;">
<h1>{{ object.title }}</h1>
{% if object.introduction %}
<p>{{ object.introduction }}
{% endif %}
{% for section in object.sections.all %}
<h2>{{ section.title }}</h2>
{% if section.introduction %}
<p>{{ section.introduction }}
{% endif %}
{% if section.type == 'budget' %}
{% include 'reports/full/budget.html' with section=section %}
{% elif section.type == 'balance' %}
{% include 'reports/full/balance.html' with section=section balance=section.balance %}
{% endif %}
<p class="row-padding"></p>
{% endfor %}
</div>
</div>
{% endblock %}
{% extends 'base/base.html' %}
{% load i18n %}
{% block page_title %}{% trans 'Really Delete?' %}{% endblock %}
{% block content %}
<h1 class="display-4">{% trans 'Really delete connection?' %}</h1>
<h4>{% blocktrans with object=object %}Are you sure you want to delete {{ object }}?{% endblocktrans %}</h4>
<form method="post" action="">
{% csrf_token %}
<p>
<input class="btn btn-success" type="submit" name="yes" value="{% trans 'Yes' %}" />
<a href="{% url 'reports:list' %}" class="btn btn-danger">{% trans 'No' %}</a>
</p>
</form>
{% endblock %}
{% extends 'base/base.html' %}
{% load i18n %}
{% block page_title %}{% trans 'Report' %} '{{ report.title }}' {% trans 'Overleaf connection' %}{% endblock %}
{% block content %}
<h1 class="display-4">{% trans 'Report' %} '{{ report.title }}' {% trans 'Overleaf connection' %}</h1>
{% comment %} <p><a href="{% url report.get_absolute_url %}" class="btn btn-secondary">{% trans 'Back' %}</a></p> {% endcomment %}
{% if form.errors %}
{% for error in form.non_field_errors %}
<div class="alert alert-danger">
{{ error|escape }}
</div>
{% endfor %}
{% endif %}
<form action="" method="post">
{% csrf_token %}
<div class="row">
<div class="col-sm-12 col-10 col-lg-8 col-xl-6">
{% trans 'You can sync your report with Overleaf. All data in the folder \'quaestor\' of your Overleaf project will be overwritten.' %}
<div class="form-group" style="margin-top: 20px;">
<label for="{{ form.url.id_for_label }}">{{ form.url.label }}</label>
<input type="text" class="form-control {% if form.url.errors %}is-invalid{% endif %}" id="{{ form.url.id_for_label }}" name="{{ form.url.html_name }}" maxlength="255" required {% if form.url.value %} placeholder="https://git.overleaf.com/00a5f71fe28e6fb1d8fd070a" value="{{ form.url.value }}"{% endif %} />
{% if form.url.errors %}
<div class="invalid-feedback">{{ form.url.errors.0|escape }}</div>
{% else %}
<small class="form-text text-muted">{% trans 'See Menu -> Git and copy the URL.' %}</small>
{% endif %}
</div>
<div class="form-group" style="margin-top: 20px;">
<label for="{{ form.email.id_for_label }}">{{ form.email.label }}</label>
<input type="text" class="form-control {% if form.email.errors %}is-invalid{% endif %}" id="{{ form.email.id_for_label }}" name="{{ form.email.html_name }}" maxlength="255" required {% if form.email.value %} placeholder="https://git.overleaf.com/00a5f71fe28e6fb1d8fd070a" value="{{ form.email.value }}"{% endif %} />
{% if form.email.errors %}
<div class="invalid-feedback">{{ form.email.errors.0|escape }}</div>
{% endif %}
</div>
<div class="form-group">
<label for="{{ form.password.id_for_label }}">{{ form.password.label }}</label>
<input type="password" autocomplete="off" class="form-control {% if form.password.errors %}is-invalid{% endif %}" id="{{ form.password.id_for_label }}" name="{{ form.password.html_name }}" maxlength="255" required {% if form.password.value %} placeholder="https://git.overleaf.com/00a5f71fe28e6fb1d8fd070a" value="{{ form.password.value }}"{% endif %} />
{% if form.password.errors %}
<div class="invalid-feedback">{{ form.password.errors.0|escape }}</div>
{% else %}
<small class="form-text text-muted">{% trans 'You will be asked for your password everytime you sync. Be warned that your password might be temporarily stored plain on disk.' %}</small>
{% endif %}
</div>
<button type="submit" class="btn btn-success">{% if object %}{% trans 'Save' %}{% else %}{% trans 'Create' %}{% endif %}</button>
</div>
</div>
</form>
{% endblock %}
{% extends 'base/base.html' %}
{% load i18n %}
{% block page_title %}{% trans 'Really Delete?' %}{% endblock %}
{% block content %}
<h1 class="display-4">{% trans 'Really delete report?' %}</h1>
<h4>{% blocktrans with object=object %}Are you sure you want to delete {{ object }}?{% endblocktrans %}</h4>
<p>{% trans 'This might delete some important information!' %}</p>
<form method="post" action="">
{% csrf_token %}
<p>
<input class="btn btn-success" type="submit" name="yes" value="{% trans 'Yes' %}" />
<a href="{% url 'reports:detail' pk=report.pk %}" class="btn btn-danger">{% trans 'No' %}</a>
</p>
</form>
{% endblock %}
{% extends 'base/base.html' %}
{% load i18n %}
{% load popover %}
{% block page_title %}{% trans 'Reports' %}{% endblock %}
{% block content %}
<h1 class="display-4">{% trans 'Report' %} '{{ object.title }}'</h1>
<p>
<a href="{% url 'reports:list' %}" class="btn btn-secondary">{% trans 'Back' %}</a>
<a href="{% url 'reports:update' object.pk %}" class="btn btn-secondary">{% trans 'Update report' %}</a>
<a href="{% url 'reports:section_create' object.pk %}" class="btn btn-secondary">{% trans 'Add section' %}</a>
<a href="{% url 'reports:detail_full' object.pk %}" class="btn btn-secondary">{% trans 'Full report' %}</a>
<span class="float-right">
{% if not object.overleaf %}
<a href="{% url 'reports:overleaf_create' pk=object.id %}" class="btn btn-success">{% trans 'Sync to Overleaf' %}</a>
{% else %}
<a href="{% url 'reports:overleaf_delete' pk=object.id %}" class="btn btn-secondary">{% trans 'Disable syncing' %}</a>
<button type="button" class="btn btn-success" data-toggle="modal" data-target="#syncModal">{% trans 'Re-sync to Overleaf' %}</button>
<!-- Modal -->
<div class="modal fade" id="syncModal" tabindex="-1" role="dialog" aria-labelledby="syncModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<form action="{% url 'reports:overleaf_sync' pk=object.id %}" method="post">
{% csrf_token %}
<div class="modal-header">
<h5 class="modal-title" id="syncModalLabel">{% trans 'Sync to Overleaf' %}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
{% trans 'Please enter your password to start the Overleaf sync:' %}