Skip to content

Commit

Permalink
Merge branch 'htmx'
Browse files Browse the repository at this point in the history
  • Loading branch information
digitalfox committed Nov 3, 2024
2 parents 68f3018 + 52ea309 commit 96b1390
Show file tree
Hide file tree
Showing 44 changed files with 981 additions and 839 deletions.
23 changes: 13 additions & 10 deletions crm/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,17 +245,20 @@ class Meta:
"company": CompanyChoices}

def __init__(self, *args, **kwargs):
mission_id = kwargs.pop("mission_id", None)
super(MissionContactForm, self).__init__(*args, **kwargs)
self.helper.layout = Layout(Div(Column(FieldWithButtons("contact", HTML(
"<a role='button' class='btn btn-primary' href='%s' target='_blank'><i class='bi bi-plus'></i></a>" % reverse(
"crm:contact_create"))),
css_class="col-md-6"),
Column(FieldWithButtons("company", HTML(
"<a role='button' class='btn btn-primary' href='%s' target='_blank'><i class='bi bi-plus'></i></a>" % reverse(
"crm:company"))),
css_class="col-md-6"),
css_class="row"),
self.submit)
if mission_id:
self.inline_helper.form_action = reverse("crm:linked_mission_contact_create", args=[mission_id])
self.inline_helper.layout = Layout(Div(
Column(FieldWithButtons("contact",
HTML("""<a role='button' class='btn btn-primary' href='#' onclick='$("#contactForm").show("slow"); $("#contact_input_group").hide("slow")'><i class='bi bi-plus'></i></a>"""),
css_id="contact_input_group"),
css_class="col-md-6"),
Column(FieldWithButtons("company",
HTML("""<a role='button' class='btn btn-primary' href='#' onclick='$("#companyForm").show("slow"); $("#company_input_group").hide("slow")'><i class='bi bi-plus'></i></a>"""),
css_id="company_input_group"),
css_class="col-md-6"),
css_class="row"))


class BusinessBrokerForm(PydiciCrispyModelForm):
Expand Down
3 changes: 1 addition & 2 deletions crm/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ def relationData(self):
for missionContact in self.missioncontact_set.all():
for mission in missionContact.mission_set.all():
missionNode = GNode("mission-%s" % mission.id, """<i class="bi bi-gear"></i>
<span class='graph-tooltip' title='%s'><a href='%s'>&nbsp;%s&nbsp;</a></span>""" % (mission.short_name(),
<span class='graph-tooltip' title='%s'><a href='%s'>%s&nbsp;</a></span>""" % (mission.short_name(),
mission.get_absolute_url(),
mission.mission_id()))
nodes.add(missionNode)
Expand Down Expand Up @@ -488,7 +488,6 @@ def __str__(self):
def get_absolute_url(self):
return reverse("crm:contact_detail", args=[self.contact.id, ])


class Meta:
ordering = ["company", "contact"]
verbose_name = _("Mission contact")
Expand Down
1 change: 1 addition & 0 deletions crm/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
re_path(r'^contact/add/$', v.ContactCreate.as_view(), name='contact_create'),
re_path(r'^contact/(?P<pk>\d+)/update$', v.ContactUpdate.as_view(), name='contact_update'),
re_path(r'^mission/contact/add/$', v.MissionContactCreate.as_view(), name='mission_contact_create'),
re_path(r'^mission/(?P<mission_id>\d+)/addcontact/add/$', v.linked_mission_contact_create, name='linked_mission_contact_create'),
re_path(r'^mission/contact/(?P<pk>\d+)/update$', v.MissionContactUpdate.as_view(), name='mission_contact_update'),
re_path(r'^businessbroker/add/$', v.BusinessBrokerCreate.as_view(), name='businessbroker_create'),
re_path(r'^businessbroker/(?P<pk>\d+)/update$', v.BusinessBrokerUpdate.as_view(), name='businessbroker_update'),
Expand Down
44 changes: 40 additions & 4 deletions crm/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@
from crm.utils import get_subsidiary_from_session
from people.models import Consultant, ConsultantProfile
from leads.models import Lead
from staffing.models import Timesheet
from staffing.models import Timesheet, Mission
from leads.utils import leads_state_stat
from core.decorator import pydici_non_public, pydici_feature, PydiciNonPublicdMixin, PydiciFeatureMixin
from core.utils import COLORS, get_parameter
from billing.models import ClientBill
from staffing.views import mission_contacts


class ContactReturnToMixin(object):
Expand Down Expand Up @@ -68,7 +69,7 @@ def get_initial(self):
return {}


class ContactUpdate(PydiciNonPublicdMixin, ThirdPartyMixin, ContactReturnToMixin, UpdateView):
class ContactUpdate(PydiciNonPublicdMixin, ThirdPartyMixin, UpdateView):
model = Contact
template_name = "core/form.html"
form_class = ContactForm
Expand All @@ -77,7 +78,6 @@ class ContactUpdate(PydiciNonPublicdMixin, ThirdPartyMixin, ContactReturnToMixin
class ContactDelete(PydiciNonPublicdMixin, FeatureContactsWriteMixin, DeleteView):
model = Contact
template_name = "core/delete.html"
form_class = ContactForm
success_url = reverse_lazy("crm:contact_list")

def form_valid(self, form):
Expand All @@ -104,6 +104,39 @@ def contact_list(request):
"user": request.user})


def linked_mission_contact_create(request, mission_id):
missionContactForm = None
contactForm = None
companyForm = None
if request.POST:
missionContactForm = MissionContactForm(request.POST, prefix="mission-contact")
contactForm = ContactForm(request.POST, prefix="contact")
companyForm = CompanyForm(request.POST, prefix="company")

if contactForm.is_valid():
contact = contactForm.save()
missionContactForm.data = missionContactForm.data.copy()
missionContactForm.data["mission-contact-contact"] = contact.id

if companyForm.is_valid():
company = companyForm.save()
missionContactForm.data = missionContactForm.data.copy()
missionContactForm.data["mission-contact-company"] = company.id

if missionContactForm.is_valid():
mission_contact = missionContactForm.save()
mission = Mission.objects.get(id=mission_id)
mission.contacts.add(mission_contact)
mission.save()
request.method = "GET" # Fake request to return unbound form
return mission_contacts(request, mission_id)

return render(request, "crm/_mission_contact_form.html", {"mission_id": mission_id,
"missionContactForm": missionContactForm or MissionContactForm(mission_id=mission_id, prefix="mission-contact"),
"contactForm": contactForm or ContactForm(prefix="contact"),
"companyForm": companyForm or CompanyForm(prefix="company")})


class MissionContactCreate(PydiciNonPublicdMixin, FeatureContactsWriteMixin, ContactReturnToMixin, CreateView):
model = MissionContact
template_name = "core/form.html"
Expand All @@ -124,6 +157,7 @@ class BusinessBrokerCreate(PydiciNonPublicdMixin, FeatureContactsWriteMixin, Cre
def get_success_url(self):
return reverse_lazy("crm:businessbroker_list")


class BusinessBrokerUpdate(PydiciNonPublicdMixin, FeatureContactsWriteMixin, UpdateView):
model = BusinessBroker
template_name = "core/form.html"
Expand Down Expand Up @@ -377,7 +411,7 @@ def company_detail(request, company_id):

# Gather contacts for this company
business_contacts = Contact.objects.filter(client__organisation__company=company).distinct()
mission_contacts = Contact.objects.filter(missioncontact__company=company).distinct()
mission_contacts = Contact.objects.filter(missioncontact__mission__lead__client__organisation__company=company).distinct()
administrative_contacts = AdministrativeContact.objects.filter(company=company)

# Won rate
Expand Down Expand Up @@ -434,6 +468,8 @@ def company_detail(request, company_id):
"contacts_count" : business_contacts.count() + mission_contacts.count() + administrative_contacts.count(),
"clients": Client.objects.filter(organisation__company=company).select_related(),
"lead_data_url": reverse('leads:client_company_lead_table_DT', args=[company.id,]),
"supplier_lead_data_url": reverse('leads:supplier_company_lead_table_DT', args=[company.id, ]),
"businessbroker_lead_data_url": reverse('leads:businessbroker_lead_table_DT', args=[company.id,]),
"mission_data_url": reverse('staffing:client_company_mission_table_DT', args=[company.id,]),
"mission_datatable_options": ''' "columnDefs": [{ "orderable": false, "targets": [4, 8, 9, 10] },
{ className: "hidden-xs hidden-sm hidden-md", "targets": [6, 7,8]}],
Expand Down
12 changes: 11 additions & 1 deletion expense/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@ def get_queryset(self):
return expenses


class ExpenseVATForm(forms.ModelForm):
class Meta:
model = Expense
fields = ("vat",)

def __init__(self, *args, **kwargs):
super(ExpenseVATForm, self).__init__(*args, **kwargs)
self.fields["vat"].label = False
self.fields["vat"].widget.attrs["autofocus"] = True


class ExpenseForm(forms.ModelForm):
"""Expense form based on Expense model"""
class Meta:
Expand All @@ -60,7 +71,6 @@ class Meta:
"comment": Textarea(attrs={'cols': 17, 'rows': 2}), # Reduce height and increase width
}


def __init__(self, *args, **kwargs):
subcontractor = kwargs.pop("subcontractor")
super(ExpenseForm, self).__init__(*args, **kwargs)
Expand Down
19 changes: 10 additions & 9 deletions expense/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,22 +136,23 @@ def receipt_data(self):
if self.receipt:
content_type = self.receipt_content_type()
data = BytesIO()
for chunk in self.receipt.chunks():
data.write(chunk)

data = b64encode(data.getvalue()).decode()
if content_type == "application/pdf":
response = "<object data='data:application/pdf;base64,%s' type='application/pdf' width='100%%' height='100%%'></object>" % data
else:
response = "<img src='data:%s;base64,%s' class='receipt'>" % (content_type, data)
try:
for chunk in self.receipt.chunks():
data.write(chunk)
data = b64encode(data.getvalue()).decode()
if content_type == "application/pdf":
response = "<object data='data:application/pdf;base64,%s' type='application/pdf' width='100%%' height='100%%'></object>" % data
else:
response = "<img src='data:%s;base64,%s' class='receipt'>" % (content_type, data)
except FileNotFoundError:
response = "Expense file not found"

return response

def receipt_content_type(self):
if self.receipt:
return mimetypes.guess_type(self.receipt.name)[0] or "application/stream"


def get_absolute_url(self):
return reverse('expense:expense', args=[str(self.id)])

Expand Down
53 changes: 25 additions & 28 deletions expense/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@
"""

from django.utils.translation import gettext as _
from django.urls import reverse
from django.db.models import Q
from django.utils.safestring import mark_safe
from django.utils.encoding import smart_str
from django.template import Template, RequestContext
from django.template.loader import get_template
from django_datatables_view.base_datatable_view import BaseDatatableView
Expand All @@ -22,7 +20,7 @@
from core.templatetags.pydici_filters import link_to_consultant
from core.utils import TABLES2_HIDE_COL_MD, to_int_or_round
from core.decorator import PydiciFeatureMixin, PydiciNonPublicdMixin, PydiciSubcontractordMixin
from expense.utils import expense_transition_to_state_display, user_expense_perm
from expense.utils import expense_transition_to_state_display, user_expense_perm, can_edit_expense, expense_next_states


class ExpenseTableDT(PydiciSubcontractordMixin, PydiciFeatureMixin, BaseDatatableView):
Expand All @@ -37,11 +35,14 @@ class ExpenseTableDT(PydiciSubcontractordMixin, PydiciFeatureMixin, BaseDatatabl
state_template = get_template("expense/_expense_state_column.html")
ko_sign = mark_safe("""<i class="bi bi-x" style="color:red"><span class="visuallyhidden">No</span></i>""")
ok_sign = mark_safe("""<i class="bi bi-check" style="color:green"><span class="visuallyhidden">Yes</span></i>""")
vat_template = get_template("expense/_expense_vat_column.html")

def get_initial_queryset(self):
expense_administrator, expense_subsidiary_manager, expense_manager, expense_paymaster, expense_requester = user_expense_perm(self.request.user)
consultant = Consultant.objects.get(trigramme__iexact=self.request.user.username)

self.can_edit_vat = expense_administrator or expense_paymaster # Used for vat column render

if expense_subsidiary_manager:
user_team = consultant.user_team(subsidiary=True)
elif expense_manager:
Expand Down Expand Up @@ -110,7 +111,7 @@ def render_column(self, row, column):
elif column == "amount":
return to_int_or_round(row.amount, 2)
elif column == "vat":
return """<div id="{0}" class="jeditable-vat">{1}</div>""".format(row.id, row.vat)
return self.vat_template.render(context={"expense": row, "can_edit_vat": self.can_edit_vat}, request=self.request)
else:
return super(ExpenseTableDT, self).render_column(row, column)

Expand All @@ -125,11 +126,16 @@ class ExpenseTable(tables.Table):
state = tables.TemplateColumn(template_name="expense/_expense_state_column.html", orderable=False)
expense_date = tables.TemplateColumn("""<span title="{{ record.expense_date|date:"Ymd" }}">{{ record.expense_date }}</span>""") # Title attr is just used to have an easy to parse hidden value for sorting
update_date = tables.TemplateColumn("""<span title="{{ record.update_date|date:"Ymd" }}">{{ record.update_date }}</span>""", attrs=TABLES2_HIDE_COL_MD) # Title attr is just used to have an easy to parse hidden value for sorting
vat = tables.TemplateColumn("""{% load l10n %}<div id="{{record.id|unlocalize}}" class="jeditable-vat">{{record.vat}}</div>""")
transitions_template = get_template("expense/_expense_transitions_column.html")
vat_template = get_template("expense/_expense_vat_column.html")


def render_user(self, value):
return link_to_consultant(value)

def render_vat(self, record):
return self.vat_template.render(context={"expense": record, "can_edit_vat": True})

class Meta:
model = Expense
sequence = ("id", "user", "description", "lead", "amount", "vat", "chargeable", "corporate_card", "receipt", "state", "expense_date", "update_date", "comment")
Expand All @@ -142,35 +148,18 @@ class Meta:
class ExpenseWorkflowTable(ExpenseTable):
transitions = tables.Column(accessor="pk")

def render_transitions(self, record):
result = []
for transition in self.transitionsData[record.id]:
result.append("""<a role='button' title='%s' class='btn btn-primary btn-sm' href="javascript:;" onClick="$.get('%s', process_expense_transition)">%s</a>"""
% (expense_transition_to_state_display(transition), reverse("expense:update_expense_state", args=[record.id, transition]), expense_transition_to_state_display(transition)[0:2]))
if self.expenseEditPerm[record.id]:
result.append("<a role='button' title='%s' class='btn btn-primary btn-sm' href='%s'>%s</a>"
% (smart_str(_("Edit")),
reverse("expense:expenses", kwargs={"expense_id": record.id}),
# Translators: Ed is the short term for Edit
smart_str(_("Ed"))))
result.append("<a role='button' title='%s' class='btn btn-primary btn-sm' href='%s'>%s</a>" %
(smart_str(_("Delete")),
reverse("expense:expense_delete", kwargs={"expense_id": record.id}),
# Translators: De is the short term for Delete
smart_str(_("De"))))
result.append("<a role='button' title='%s' class='btn btn-primary btn-sm' href='%s'>%s</a>" %
(smart_str(_("Clone")),
reverse("expense:clone_expense", kwargs={"clone_from": record.id}),
# Translators: Cl is the short term for Clone
smart_str(_("Cl"))))
return mark_safe(" ".join(result))

class Meta:
sequence = ("id", "user", "description", "lead", "amount", "chargeable", "corporate_card", "receipt", "state", "transitions", "expense_date", "update_date", "comment")
fields = sequence


class UserExpenseWorkflowTable(ExpenseWorkflowTable):
def render_transitions(self, record):
return self.transitions_template.render(context={"record": record,
"transitions": [],
"expense_edit_perm": can_edit_expense(record, self.request.user)},
request=self.request)

class Meta:
attrs = {"class": "pydici-tables2 table table-hover table-striped table-sm", "id": "user_expense_workflow_table"}
prefix = "user_expense_workflow_table"
Expand All @@ -180,6 +169,14 @@ class Meta:
class ManagedExpenseWorkflowTable(ExpenseWorkflowTable):
description = tables.TemplateColumn("""{% load l10n %} <span id="managed_expense_{{record.id|unlocalize }}">{{ record.description }}</span>""",
attrs={"td": {"class": "description"}})

def render_transitions(self, record):
transitions = [(t, expense_transition_to_state_display(t), expense_transition_to_state_display(t)[0:2]) for t in expense_next_states(record, self.request.user)]
return self.transitions_template.render(context={"record": record,
"transitions": transitions,
"expense_edit_perm": can_edit_expense(record, self.request.user)},
request=self.request)

class Meta:
attrs = {"class": "pydici-tables2 table table-hover table-striped table-sm", "id": "managed_expense_workflow_table"}
prefix = "managed_expense_workflow_table"
Expand Down
2 changes: 1 addition & 1 deletion expense/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
re_path(r'^(?P<expense_id>\d+)/receipt$', v.expense_receipt, name="expense_receipt"),
re_path(r'^(?P<expense_id>\d+)/delete$', v.expense_delete, name="expense_delete"),
re_path(r'^(?P<expense_id>\d+)/change$', v.expenses, name="expenses"),
re_path(r'^(?P<expense_id>\d+)/expense_vat$', v.update_expense_vat, name="update_expense_vat"),
re_path(r'^(?P<expense_id>\d+)/(?P<target_state>\w+)$', v.update_expense_state, name="update_expense_state"),
re_path(r'^expense_vat$', v.update_expense_vat, name="update_expense_vat"),
re_path(r'^clone/(?P<clone_from>\d+)$', v.expenses, name="clone_expense"),
re_path(r'^mission/(?P<lead_id>\d+)$', v.lead_expenses, name="lead_expenses"),
re_path(r'^history/?$', v.expenses_history, name="expenses_history"),
Expand Down
Loading

0 comments on commit 96b1390

Please sign in to comment.