mirror of
https://github.com/Nebucatnetzer/network_inventory.git
synced 2024-06-26 00:59:10 +02:00
commit
ccafdf1c99
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -144,6 +144,7 @@ celerybeat-schedule
|
|||
# virtualenv
|
||||
.venv
|
||||
venv/
|
||||
venv
|
||||
ENV/
|
||||
|
||||
# Spyder project settings
|
||||
|
|
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"python.testing.unittestEnabled": false,
|
||||
"python.testing.pytestEnabled": true,
|
||||
"python.pythonPath": "./venv/bin/python",
|
||||
"python.pythonPath": "./venv/bin/python3",
|
||||
"python.linting.pylintEnabled": false,
|
||||
"python.linting.flake8Enabled": true,
|
||||
"python.linting.enabled": true,
|
||||
|
|
23
Makefile
23
Makefile
|
@ -5,17 +5,13 @@ SHELL=/usr/bin/env bash
|
|||
.PHONY: run
|
||||
run: setup
|
||||
( \
|
||||
source venv/bin/activate; \
|
||||
export DJANGO_SETTINGS_MODULE=network_inventory.settings.local; \
|
||||
find . -name __pycache__ -o -name "*.pyc" -delete; \
|
||||
python manage.py runserver; \
|
||||
)
|
||||
|
||||
.PHONY: setup
|
||||
setup: ./venv
|
||||
setup:
|
||||
( \
|
||||
source venv/bin/activate; \
|
||||
export DJANGO_SETTINGS_MODULE=network_inventory.settings.local; \
|
||||
docker-compose -f docker-compose-development.yml up -d; \
|
||||
if [ -f .second_run ]; then \
|
||||
sleep 2; \
|
||||
|
@ -46,12 +42,9 @@ setup: ./venv
|
|||
fi; \
|
||||
)
|
||||
|
||||
./venv:
|
||||
python3 -m venv venv
|
||||
( \
|
||||
source venv/bin/activate; \
|
||||
pip3 install -r requirements/local.txt; \
|
||||
)
|
||||
venv:
|
||||
nix build .#venv -o venv
|
||||
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
|
@ -64,28 +57,22 @@ clean:
|
|||
.PHONY: cleanall
|
||||
cleanall: clean
|
||||
docker-compose -f docker-compose-development.yml down -v --rmi local
|
||||
rm -rf venv/
|
||||
rm venv
|
||||
|
||||
.PHONY: init
|
||||
init:
|
||||
( \
|
||||
source venv/bin/activate; \
|
||||
export DJANGO_SETTINGS_MODULE=network_inventory.settings.local; \
|
||||
python manage.py loaddata network_inventory.yaml; \
|
||||
)
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
( \
|
||||
source venv/bin/activate; \
|
||||
export DJANGO_SETTINGS_MODULE=network_inventory.settings.local; \
|
||||
pytest -nauto --nomigrations --cov=. --cov-report=html; \
|
||||
)
|
||||
|
||||
.PHONY: debug
|
||||
debug:
|
||||
( \
|
||||
source venv/bin/activate; \
|
||||
export DJANGO_SETTINGS_MODULE=network_inventory.settings.local; \
|
||||
pytest --pdb --nomigrations --cov=. --cov-report=html; \
|
||||
)
|
||||
|
|
|
@ -2,10 +2,11 @@ from django import forms
|
|||
from django.urls import reverse_lazy
|
||||
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Layout, Submit, Button, Field
|
||||
from crispy_forms.layout import Submit, Button
|
||||
from crispy_forms.bootstrap import FormActions
|
||||
|
||||
from .models import Customer
|
||||
from core import utils
|
||||
from .models import Customer, DummyLocation, Location
|
||||
|
||||
|
||||
class CustomerForm(forms.ModelForm):
|
||||
|
@ -30,3 +31,43 @@ class CustomerForm(forms.ModelForm):
|
|||
Button('cancel', 'Cancel', css_class="btn btn-secondary",
|
||||
onclick="closeModal()")
|
||||
))
|
||||
|
||||
|
||||
class LocationForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Location
|
||||
fields = (
|
||||
'name',
|
||||
'customer'
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
user = kwargs.pop("user")
|
||||
super(LocationForm, self).__init__(*args, **kwargs)
|
||||
"""
|
||||
If the user is not a superuser it's always assigned to a customer which
|
||||
we can use to assign to the field.
|
||||
"""
|
||||
self.fields['customer'].queryset = (
|
||||
utils.objects_for_allowed_customers(
|
||||
Customer, user=user))
|
||||
|
||||
self.helper = FormHelper(self)
|
||||
self.helper.attrs = {
|
||||
'hx-post': reverse_lazy('htmx_create_location'),
|
||||
'id': 'location-form',
|
||||
}
|
||||
self.helper.layout.append(
|
||||
FormActions(
|
||||
Submit('save_location', 'Save'),
|
||||
Button('cancel', 'Cancel', css_class="btn btn-secondary",
|
||||
onclick="closeModal()")
|
||||
))
|
||||
|
||||
|
||||
class DummyLocationForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = DummyLocation
|
||||
fields = (
|
||||
'location',
|
||||
)
|
||||
|
|
|
@ -33,3 +33,10 @@ class Location(models.Model):
|
|||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class DummyLocation(models.Model):
|
||||
location = models.ForeignKey(Location, on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self):
|
||||
return self.location
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{% include "core/partials/modal.html" with form=form modal_title="Add Location"%}
|
|
@ -0,0 +1,6 @@
|
|||
{% if valid %}
|
||||
<div hx-swap-oob="true:#htmx-location-target">{{ form }}</div>
|
||||
<div id="htmx-modal-position" hx-swap-oob="true"></div>
|
||||
{% else %}
|
||||
{{ form }}
|
||||
{% endif %}
|
25
customers/tests/test_location_form.py
Normal file
25
customers/tests/test_location_form.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
import pytest
|
||||
from mixer.backend.django import mixer
|
||||
|
||||
from customers import forms
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_location_form(create_admin_user):
|
||||
fixture = create_admin_user()
|
||||
user = fixture['admin']
|
||||
form = forms.LocationForm(user=user, data={})
|
||||
assert form.is_valid() is False, (
|
||||
"Should be false because no data was given")
|
||||
|
||||
data = {"name": "Main Office",
|
||||
"customer": 3}
|
||||
form = forms.LocationForm(user=user, data=data)
|
||||
assert form.is_valid() is False, (
|
||||
"Should be false because the customer doesn't exist.")
|
||||
|
||||
data = {"name": mixer.blend('customers.Location').name,
|
||||
"customer": fixture['customer'].id}
|
||||
form = forms.LocationForm(user=user, data=data)
|
||||
assert form.is_valid() is True, ("Should be valid with the given data.")
|
40
customers/tests/test_location_form_view.py
Normal file
40
customers/tests/test_location_form_view.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
from django.test import Client
|
||||
from mixer.backend.django import mixer
|
||||
import pytest
|
||||
|
||||
from core.tests import helper
|
||||
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_load_htmx_create_location_view(create_admin_user):
|
||||
create_admin_user()
|
||||
client = Client()
|
||||
client.login(username="pharma-admin", password="password")
|
||||
url = '/create/location/'
|
||||
response = client.get(url)
|
||||
assert (response.status_code == 200
|
||||
and helper.in_content(response, 'Add Location'))
|
||||
|
||||
|
||||
def test_htmx_create_location_view(create_admin_user):
|
||||
create_admin_user()
|
||||
client = Client()
|
||||
client.login(username="pharma-admin", password="password")
|
||||
data = {"name": mixer.faker.name(),
|
||||
"save_location": 1}
|
||||
response = client.post('/create/location/', data)
|
||||
assert (response.status_code == 200
|
||||
and helper.in_content(response, data["name"]))
|
||||
|
||||
|
||||
def test_htmx_create_location_view_invalid_form(create_admin_user):
|
||||
create_admin_user()
|
||||
client = Client()
|
||||
client.login(username="pharma-admin", password="password")
|
||||
data = {"name": "",
|
||||
"save_location": 1}
|
||||
response = client.post('/create/location/', data)
|
||||
assert (response.status_code == 200
|
||||
and helper.in_content(response, "This field is required."))
|
|
@ -11,4 +11,6 @@ urlpatterns = [
|
|||
path('create/customer/',
|
||||
views.create_customer,
|
||||
name='customer_create'),
|
||||
path('create/location/', views.htmx_create_location,
|
||||
name='htmx_create_location')
|
||||
]
|
||||
|
|
|
@ -3,13 +3,19 @@ from django.contrib.auth.decorators import login_required
|
|||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.http.response import HttpResponse
|
||||
from django.shortcuts import render
|
||||
from django.template.context_processors import csrf
|
||||
from django.template.response import TemplateResponse
|
||||
from django.urls import reverse
|
||||
from django.views.generic import DeleteView
|
||||
|
||||
from crispy_forms.utils import render_crispy_form
|
||||
from crispy_forms.templatetags.crispy_forms_filters import as_crispy_field
|
||||
|
||||
from core import utils
|
||||
from .forms import CustomerForm
|
||||
from .models import Customer
|
||||
from .forms import DummyLocationForm
|
||||
from .forms import LocationForm
|
||||
from .models import Customer, DummyLocation
|
||||
from .tables import CustomersTable
|
||||
|
||||
|
||||
|
@ -57,3 +63,30 @@ class CustomerDeleteView(LoginRequiredMixin, DeleteView):
|
|||
|
||||
def get_success_url(self):
|
||||
return reverse('customers')
|
||||
|
||||
|
||||
@login_required
|
||||
def htmx_create_location(request):
|
||||
context = {}
|
||||
user = request.user
|
||||
if request.method == "POST" and 'save_location' in request.POST:
|
||||
form = LocationForm(request.POST, user=user)
|
||||
if form.is_valid():
|
||||
location = form.save(commit=True)
|
||||
dummy_model = DummyLocation()
|
||||
dummy_model.location = location
|
||||
dummy_form = DummyLocationForm(instance=dummy_model)
|
||||
form_html = as_crispy_field(dummy_form["location"])
|
||||
else:
|
||||
context.update(csrf(request))
|
||||
form.helper.attrs['hx-swap-oob'] = 'true'
|
||||
form_html = render_crispy_form(form)
|
||||
context["valid"] = form.is_valid()
|
||||
context['form'] = form_html
|
||||
template_path = "customers/partials/location_response.html"
|
||||
return TemplateResponse(request, template_path, context)
|
||||
|
||||
form = LocationForm(user=user)
|
||||
context["form"] = form
|
||||
template_path = "customers/partials/location_create.html"
|
||||
return TemplateResponse(request, template_path, context)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from django.urls import reverse_lazy
|
||||
import floppyforms.__future__ as forms
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Layout, Submit, HTML, Field, Button, Div
|
||||
from crispy_forms.layout import Layout, Submit, HTML, Button, Div
|
||||
from crispy_forms.bootstrap import FormActions
|
||||
|
||||
from core import utils
|
||||
|
@ -80,11 +80,25 @@ class DeviceUpdateForm(forms.ModelForm):
|
|||
self.helper.layout = Layout(
|
||||
'name',
|
||||
'customer',
|
||||
'location',
|
||||
'user',
|
||||
Div(Field('category'),
|
||||
Div(
|
||||
Div('location', id="htmx-location-target"),
|
||||
HTML("""
|
||||
<a hx-get="{% url 'device_category_create' %}" hx-swap="innerHTML" hx-target="#htmx-modal-position" href="" class="add" title="Add" data-toggle="tooltip"><i class="material-icons">add</i></a>
|
||||
<a hx-get="{% url 'htmx_create_location' %}"
|
||||
hx-swap="innerHTML" hx-target="#htmx-modal-position"
|
||||
href=""
|
||||
class="add" title="Add" data-toggle="tooltip"><i
|
||||
class="material-icons">add</i></a>
|
||||
"""),
|
||||
css_class="input-group"),
|
||||
'user',
|
||||
Div(
|
||||
Div('category', id="htmx-category-target"),
|
||||
HTML("""
|
||||
<a hx-get="{% url 'device_category_create' %}"
|
||||
hx-swap="innerHTML" hx-target="#htmx-modal-position"
|
||||
href=""
|
||||
class="add" title="Add" data-toggle="tooltip"><i
|
||||
class="material-icons">add</i></a>
|
||||
"""),
|
||||
css_class="input-group"),
|
||||
'serialnumber',
|
||||
|
@ -92,7 +106,8 @@ class DeviceUpdateForm(forms.ModelForm):
|
|||
FormActions(
|
||||
Submit('save_device', 'Save'),
|
||||
HTML(
|
||||
"""<a href="{{ request.META.HTTP_REFERER }}" class="btn btn-secondary">Cancel</a>""")
|
||||
"""<a href="{{ request.META.HTTP_REFERER }}"
|
||||
class="btn btn-secondary">Cancel</a>""")
|
||||
),
|
||||
Div(css_id='htmx-modal-position', css_class="col")
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{% if valid %}
|
||||
<div hx-swap-oob="true:#div_id_category">{{ form }}</div>
|
||||
<div hx-swap-oob="true:#htmx-category-target">{{ form }}</div>
|
||||
<div id="htmx-modal-position" hx-swap-oob="true"></div>
|
||||
{% else %}
|
||||
{{ form }}
|
||||
|
|
|
@ -4,7 +4,6 @@ import pytest
|
|||
from mixer.backend.django import mixer
|
||||
|
||||
from core.tests import helper
|
||||
from devices.models import DeviceCategory
|
||||
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
|
|
@ -36,5 +36,5 @@ urlpatterns = [
|
|||
name='device_in_net_delete'),
|
||||
path('warranties/', views.warranties_view, name='warranties'),
|
||||
path('create/devices/category/', views.htmx_create_device_cagetory,
|
||||
name='device_category_create')
|
||||
name='device_category_create'),
|
||||
]
|
||||
|
|
29
flake.nix
29
flake.nix
|
@ -15,20 +15,31 @@
|
|||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
machNix = mach-nix.lib."${system}";
|
||||
in
|
||||
{
|
||||
devShell = machNix.mkPythonShell {
|
||||
packagesExtra = with pkgs; [ pkgs.gnumake ];
|
||||
devEnvironment = machNix.mkPython {
|
||||
requirements = builtins.readFile ./requirements/local.txt;
|
||||
_.pytest-cov.propagatedBuildInputs.mod = pySelf: self: oldVal: oldVal ++ [ pySelf.tomli ];
|
||||
};
|
||||
in
|
||||
{
|
||||
devShell = pkgs.mkShell {
|
||||
buildInputs = [
|
||||
devEnvironment
|
||||
pkgs.gnumake
|
||||
];
|
||||
shellHook = ''
|
||||
export DJANGO_SETTINGS_MODULE=network_inventory.settings.local
|
||||
'';
|
||||
};
|
||||
packages.venv = devEnvironment;
|
||||
defaultPackage = (machNix.mkDockerImage {
|
||||
packagesExtra = with pkgs; [ pkgs.bash ];
|
||||
packagesExtra = with pkgs;
|
||||
[ pkgs.bash ];
|
||||
requirements = builtins.readFile ./requirements/docker.txt;
|
||||
_.pytest-cov.propagatedBuildInputs.mod = pySelf: self: oldVal: oldVal ++ [ pySelf.tomli ];
|
||||
}).override (oldAttrs: {
|
||||
name = "network-inventory";
|
||||
config.Cmd = [ "run.sh" ];
|
||||
});
|
||||
}).override
|
||||
(oldAttrs: {
|
||||
name = "network-inventory";
|
||||
config.Cmd = [ "run.sh" ];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user