diff --git a/.DS_Store b/.DS_Store index b4606620..d4706b1a 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml new file mode 100644 index 00000000..55b966ef --- /dev/null +++ b/.idea/sqldialects.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/car_inventory/urls.py b/car_inventory/urls.py index 400f33c0..7fa793f5 100644 --- a/car_inventory/urls.py +++ b/car_inventory/urls.py @@ -28,6 +28,7 @@ urlpatterns += i18n_patterns( path('appointment/', include('appointment.urls')), path('plans/', include('plans.urls')), path("schema/", Schema.as_view()), + path('tours/', include('tours.urls')), # path('', include(tf_urls)), ) diff --git a/database.sqlite b/database.sqlite new file mode 100644 index 00000000..e69de29b diff --git a/haikal_kb.yaml b/haikal_kb.yaml new file mode 100644 index 00000000..7a97e484 --- /dev/null +++ b/haikal_kb.yaml @@ -0,0 +1,2950 @@ +metadata: + system_name: Haikal + version: '1.0' + generated_from: Django +features: + auth_login: + description: 'Persist a user id and a backend in the request. This way a user + doesn''t + + have to reauthenticate on every request. Note that data set during + + the anonymous session is retained when the user logs in.' + source: django.contrib.auth.views.auth_login + type: view_function + auth_logout: + description: 'Remove the authenticated user''s ID from the request and flush their + session + + data.' + source: django.contrib.auth.views.auth_logout + type: view_function + csrf_protect: + description: 'This decorator adds CSRF protection in exactly the same way as + + CsrfViewMiddleware, but it can be used on a per view basis. Using both, or + + using the decorator multiple times, is harmless and efficient.' + source: django.contrib.auth.views.csrf_protect + type: view_function + get_current_site: + description: 'Check if contrib.sites is installed and return either the current + + ``Site`` object or a ``RequestSite`` object based on the request.' + source: allauth.socialaccount.views.get_current_site + type: view_function + get_user_model: + description: Return the User model that is active in this project. + source: appointment.views.get_user_model + type: view_function + login_not_required: + description: Decorator for views that allows access to unauthenticated requests. + source: debug_toolbar.views.login_not_required + type: view_function + login_required: + description: 'Decorator for views that checks that the user is logged in, redirecting + + to the log-in page if necessary.' + source: allauth.socialaccount.views.login_required + type: view_function + logout_then_login: + description: Log out the user if they are logged in. Then redirect to the login + page. + source: django.contrib.auth.views.logout_then_login + type: view_function + method_decorator: + description: Convert a function decorator into a method decorator + source: allauth.socialaccount.providers.google.views.method_decorator + type: view_function + never_cache: + description: Decorator that adds headers to a response so that it will never be + cached. + source: allauth.account.views.never_cache + type: view_function + redirect_to_login: + description: Redirect the user to the login page, passing the given 'next' page. + source: django.contrib.auth.views.redirect_to_login + type: view_function + resolve_url: + description: "Return a URL appropriate for the arguments passed.\n\nThe arguments\ + \ could be:\n\n * A model: the model's `get_absolute_url()` function will\ + \ be called.\n\n * A view name, possibly with arguments: `urls.reverse()`\ + \ will be used\n to reverse-resolve the name.\n\n * A URL, which will\ + \ be returned as-is." + source: django.contrib.auth.views.resolve_url + type: view_function + sensitive_post_parameters: + description: "Indicate which POST parameters used in the decorated view are sensitive,\n\ + so that those parameters can later be treated in a special way, for example\n\ + by hiding them when logging unhandled exceptions.\n\nAccept two forms:\n\n*\ + \ with specified parameters:\n\n @sensitive_post_parameters('password', 'credit_card')\n\ + \ def my_view(request):\n pw = request.POST['password']\n cc\ + \ = request.POST['credit_card']\n ...\n\n* without any specified parameters,\ + \ in which case consider all\n variables are sensitive:\n\n @sensitive_post_parameters()\n\ + \ def my_view(request)\n ..." + source: allauth.account.views.sensitive_post_parameters + type: view_function + update_session_auth_hash: + description: 'Updating a user''s password logs out all sessions for the user. + + + Take the current request and the updated user object from which the new + + session hash will be derived and update the session hash appropriately to + + prevent a password change from logging out the session from which the + + password was changed.' + source: django.contrib.auth.views.update_session_auth_hash + type: view_function + url_has_allowed_host_and_scheme: + description: 'Return ``True`` if the url uses an allowed host and a safe scheme. + + + Always return ``False`` on an empty url. + + + If ``require_https`` is ``True``, only ''https'' will be considered a valid + + scheme, as opposed to ''http'' and ''https'' with the default, ``False``. + + + Note: "True" doesn''t entail that a URL is "safe". It may still be e.g. + + quoted incorrectly. Ensure to also use django.utils.encoding.iri_to_uri() + + on the path component of untrusted URLs.' + source: django.contrib.auth.views.url_has_allowed_host_and_scheme + type: view_function + urlsafe_base64_decode: + description: 'Decode a base64 encoded string. Add back any trailing equal signs + that + + might have been stripped.' + source: appointment.views.urlsafe_base64_decode + type: view_function + urlunsplit: + description: 'Combine the elements of a tuple as returned by urlsplit() into a + + complete URL as a string. The data argument can be any five-item iterable. + + This may result in a slightly different, but equivalent URL, if the URL that + + was parsed originally had unnecessary delimiters (for example, a ? with an + + empty query; the RFC states that these are equivalent).' + source: django.contrib.auth.views.urlunsplit + type: view_function + shortcut: + description: Redirect to an object's page based on a content-type ID and an object + ID. + source: django.contrib.contenttypes.views.shortcut + type: view_function + serve: + description: "Serve static files below a given point in the directory structure\ + \ or\nfrom locations inferred from the staticfiles finders.\n\nTo use, put a\ + \ URL pattern such as::\n\n from django.contrib.staticfiles import views\n\ + \n path('', views.serve)\n\nin your URLconf.\n\nIt uses the django.views.static.serve()\ + \ view to serve the found files." + source: django.contrib.staticfiles.views.serve + type: view_function + BillDeleteView: + description: 'Deletes a specific BillModel instance based on the primary key (pk). + + This view requires the user to be logged in and have the appropriate + + permission to delete a bill. After the delete operation is successful, + + the user is redirected to the bill list view. + + + :param request: The HTTP request object. + + :type request: HttpRequest + + :param pk: Primary key of the BillModel instance to be deleted. + + :type pk: int + + :return: Redirect to the bill list view after successful deletion. + + :rtype: HttpResponseRedirect' + source: inventory.views.BillDeleteView + type: view_function + CarTransferPreviewView: + description: "Handles the preview of car transfer details and ensures that a user\ + \ has appropriate\npermissions to view the transfer based on their associated\ + \ dealer.\n\nThis view checks if the car transfer's destination dealer matches\ + \ the current user's\nassociated dealer type. If not, the user is redirected\ + \ to the car detail page. Otherwise,\nit renders the transfer preview page with\ + \ the relevant transfer details.\n\n:param request: The HTTP request object\n\ + :type request: django.http.HttpRequest\n:param car_pk: The primary key of the\ + \ car related to the transfer\n:type car_pk: int\n:param transfer_pk: The primary\ + \ key of the car transfer to preview\n:type transfer_pk: int\n:return: An HTTP\ + \ response rendering the transfer preview page or redirecting\n to the\ + \ car detail page\n:rtype: django.http.HttpResponse" + source: inventory.views.CarTransferPreviewView + type: view_function + DealerSettingsView: + description: "Handles dealer settings view where dealers can update their financial\ + \ and\npayment account settings. This view ensures validation and reassigns\ + \ form\nfields dynamically based on the dealer's account roles.\n\n:param request:\ + \ The HTTP request object received from the client. This parameter\n contains\ + \ data including HTTP headers, session, and POST data if applicable.\n:type\ + \ request: HttpRequest\n:param pk: Primary key representing the dealer for whom\ + \ the settings are being\n retrieved or modified. This identifier is used\ + \ to fetch or update dealer-\n specific settings from the database.\n:type\ + \ pk: int\n:return: An HTTP response rendering the dealer settings form or redirecting\n\ + \ to the dealer detail view after successful form submission.\n:rtype: HttpResponse" + source: inventory.views.DealerSettingsView + type: view_function + GroupDeleteview: + description: "Handles the deletion of a specific group instance. This view ensures\ + \ that only\nauthenticated users can perform the deletion. Upon successful deletion,\ + \ a\nsuccess message is displayed, and the user is redirected to the group list\ + \ page.\n\n:param request: The HTTP request object that contains metadata about\ + \ the\n request context and user information. Must be an authenticated user.\n\ + :param pk: The primary key of the group instance to be deleted.\n It specifies\ + \ which group to retrieve and delete.\n:return: The HTTP response that redirects\ + \ the user to the group list page\n after the group is successfully deleted." + source: inventory.views.GroupDeleteview + type: view_function + GroupPermissionView: + description: "Handles the view for adding or modifying permissions of a specific\ + \ group. This view\nfetches the group based on the primary key passed as a parameter,\ + \ and either displays\na form for editing permissions or processes the submitted\ + \ permissions.\n\nIf the request method is POST, the permissions of the group\ + \ are cleared and updated\nbased on the submitted data. A success message is\ + \ displayed upon completion, and\nthe user is redirected to the group's detail\ + \ page.\n\nIn case of a GET request, the view renders the form pre-filled with\ + \ the group's\ncurrent permissions.\n\n:param request: The HTTP request object.\n\ + :type request: HttpRequest\n:param pk: The primary key of the group whose permissions\ + \ are being modified.\n:type pk: int\n:return: The HTTP response depending on\ + \ the request type. For GET requests, renders\n the permission form for the\ + \ specified group. For POST requests, clears and updates\n the group's permissions\ + \ and redirects to the group's detail page.\n:rtype: HttpResponse" + source: inventory.views.GroupPermissionView + type: view_function + JournalEntryDeleteView: + description: "Handles the deletion of a specific journal entry. This view facilitates\n\ + the deletion of a journal entry identified by its primary key (pk). If the\n\ + deletion is successful, the user is redirected to the list of journal entries\n\ + for the associated ledger. If the deletion cannot proceed, an error message\n\ + is displayed, and the user is redirected back to the journal entry list.\n\n\ + :param request: The HTTP request object.\n:type request: HttpRequest\n:param\ + \ pk: The primary key (pk) of the journal entry to be deleted.\n:type pk: int\n\ + :return: A rendered HTML response for GET requests or a redirect upon\n \ + \ successful/failed deletion.\n:rtype: HttpResponse" + source: inventory.views.JournalEntryDeleteView + type: view_function + JournalEntryTransactionsView: + description: "Handles the retrieval and display of journal entry transactions\ + \ for a specific journal\nentry instance. It retrieves the journal entry and\ + \ its associated transactions, ordering\nthe transactions by account code. The\ + \ data is then rendered using the specified template.\n\n:param request: The\ + \ HTTP request object.\n:type request: django.http.HttpRequest\n:param pk: The\ + \ primary key of the journal entry to be retrieved.\n:type pk: int\n:return:\ + \ An HTTP response with the rendered template, including the journal entry and\n\ + \ its transactions.\n:rtype: django.http.HttpResponse" + source: inventory.views.JournalEntryTransactionsView + type: view_function + LeadDeleteView: + description: 'Handles the deletion of a Lead along with its associated customer + and potentially + + a related user account in the system. Ensures proper permissions and login + + are required before the operation is performed. Provides a success message + + after the deletion is complete. + + + :param request: The HTTP request object specific to the current user. + + :param pk: The primary key identifier of the Lead to be deleted. + + :return: An HTTP redirect response to the lead list page.' + source: inventory.views.LeadDeleteView + type: view_function + OrganizationDeleteView: + description: 'Handles the deletion of an organization based on the provided primary + key (pk). Looks up + + the organization and its corresponding user by email, attempts to delete both, + and provides + + appropriate success or error feedback to the user. In case of failure, an error + message is shown, + + while successful deletion redirects to the organization list. + + + :param request: The HTTP request object containing metadata about the request. + + :type request: HttpRequest + + :param pk: The primary key of the organization to be deleted. + + :type pk: int + + :return: An HTTP response redirecting to the organization list view. + + :rtype: HttpResponseRedirect' + source: inventory.views.OrganizationDeleteView + type: view_function + PaymentCreateView: + description: "Handles the creation of a payment entry associated with an invoice\ + \ or bill. Validates\nthe payment data against the model's current state and\ + \ reflects the changes in\ninvoice or bill records. Provides appropriate error\ + \ messages for invalid conditions\nsuch as exceeding payable amounts or attempting\ + \ payment for already fully paid models.\n\nIf successfully processed, the payment\ + \ details are saved, and the model is updated\naccordingly. This view regulates\ + \ payment for dealer-associated entities while\nensuring the model consistency.\n\ + \nThe view renders a form to submit payment details, and pre-populates the form\ + \ fields\nwith default data for the associated model if necessary.\n\n:param\ + \ request: The HTTP request object containing user request data and session\n\ + \ information. This is required to handle the request and apply the appropriate\n\ + \ processing rules.\n:param pk: The primary key of the invoice or bill being\ + \ processed. It is used to\n load the appropriate model instance for payment\ + \ processing.\n:return: An HTTP response object. Depending on the circumstances,\ + \ the response may\n redirect to the detail view of the processed invoice\ + \ or bill, re-render the\n payment form with error messages or indicate success\ + \ in payment creation." + source: inventory.views.PaymentCreateView + type: view_function + PaymentDetailView: + description: "This function handles the detail view for a payment by fetching\ + \ a journal entry\nand its associated transactions. It ensures that the request\ + \ is authenticated\nand the user has permission to view the journal entry model.\n\ + \n:param request: The HTTP request object.\n:type request: HttpRequest\n:param\ + \ pk: The primary key of the journal entry for which details are to be fetched.\n\ + :type pk: int\n:return: An HTTP response rendering the payment details template\ + \ with the journal\n entry and its associated transactions.\n:rtype:\ + \ HttpResponse" + source: inventory.views.PaymentDetailView + type: view_function + PaymentListView: + description: 'Handles the view for listing payment information associated with + the journals of a specific + + entity. This view is protected to ensure only authenticated and authorized users + can + + access it. + + + The function retrieves the related dealer object based on the current user session, + extracts + + the associated entity, and fetches all journal entries linked to the entity. + This data is + + then passed into the template for rendering. + + + :param request: The HTTP request object containing user context. + + :type request: HttpRequest + + + :return: The rendered HTML response displaying the list of payments. + + :rtype: HttpResponse' + source: inventory.views.PaymentListView + type: view_function + UserDeleteview: + description: 'Deletes a user and its associated staff member from the database + and redirects + + to the user list page. Displays a success message upon successful deletion + + of the user. + + + :param request: The HTTP request object representing the incoming request. + + :param pk: The primary key (ID) of the staff member to be deleted. + + :return: An HTTP redirect to the user list page.' + source: inventory.views.UserDeleteview + type: view_function + UserGroupView: + description: "Handles the assignment of user groups to a specific staff member.\ + \ This view\nallows updating the groups a staff member belongs to via a form\ + \ submission.\nIt processes both GET and POST requests, ensuring appropriate\ + \ group\nassignments are managed and feedback is provided to the user via messages.\n\ + \n:param request: HttpRequest object representing the HTTP request.\n:type request:\ + \ HttpRequest\n:param pk: Primary key of the staff member whose groups are being\ + \ updated.\n:type pk: int\n\n:return: Renders the user group form for GET requests\ + \ or redirects to the\n user detail page after successful submission for\ + \ POST requests.\n:rtype: HttpResponse or HttpResponseRedirect" + source: inventory.views.UserGroupView + type: view_function + account_delete: + description: 'Handles the deletion of an account object identified by its primary + key (pk). Ensures + + that the user has the necessary permissions to perform the deletion. Successfully + + deletes the account and redirects to the account list view with a success message. + + + :param request: The HTTP request object representing the current user and request + data. + + :type request: HttpRequest + + :param pk: The primary key of the account to be deleted. + + :type pk: int + + :return: An HTTP redirect response to the account list page. + + :rtype: HttpResponse' + source: inventory.views.account_delete + type: view_function + accruable_net_summary: + description: 'A convenience function that computes current net summary of accruable + models. + + "net_30" group indicates the total amount is due in 30 days or less. + + "net_0" group indicates total past due amount. + + + :param queryset: Accruable Objects Queryset. + + :return: A dictionary summarizing current net summary 0,30,60,90,90+ bill open + amounts.' + source: django_ledger.views.accruable_net_summary + type: view_function + add_activity_to_customer: + description: "Adds an activity to a specific customer.\n\nThis function allows\ + \ adding a new activity to a customer identified by their\nprimary key (`pk`).\ + \ It retrieves the customer object, processes the form for\nactivity creation,\ + \ and saves it. If the request method is POST, it validates\nthe form and associates\ + \ the activity with the respective customer. Upon\nsuccessful save, it redirects\ + \ to the customer detail view. If the request\nmethod is GET, it renders a form\ + \ for activity submission.\n\n:param request: The HTTP request object containing\ + \ metadata about the request.\n:type request: HttpRequest\n:param pk: The primary\ + \ key of the customer to which the activity will be added.\n:type pk: int\n\ + :return: An HTTP response rendered with the activity form in the context of\n\ + \ the customer, or a redirect response to the customer detail view upon\n\ + \ successful activity creation.\n:rtype: HttpResponse" + source: inventory.views.add_activity_to_customer + type: view_function + add_activity_to_lead: + description: 'Handles the process of adding a new activity to a specific lead. + This includes + + rendering a form for user input, validating the form submission, and saving + the + + new activity if the input is valid. If the method is GET, it will simply + + render the form. If the method is POST, it checks the form validity, creates + + the activity, associates it with the lead, and saves it to the database. + + + :param request: The HTTP request object containing metadata about the request + made + + :param pk: The primary key of the lead to which the activity is to be added + + :return: An HTTP response that either renders the form or redirects to the lead + detail page' + source: inventory.views.add_activity_to_lead + type: view_function + add_note_to_customer: + description: "This function allows authenticated users to add a note to a specific\ + \ customer. The\nnote creation is handled by a form, which is validated after\ + \ submission. If the form\nis valid, the note is saved and associated with the\ + \ specified customer. On successful\nsubmission, the user is redirected to the\ + \ customer detail page. If the request method\nis not POST, an empty form is\ + \ rendered.\n\n:param request: The HTTP request object containing metadata and\ + \ the method type\n (e.g., GET, POST). Should be an HttpRequest\ + \ instance.\n:param customer_id: The unique identifier (UUID) of the customer\ + \ to whom the note\n is to be added.\n:return: An HTTP response.\ + \ In the case of a successful POST request, the function\n returns a\ + \ redirect response to the customer detail page. For a GET or invalid\n \ + \ POST request, it renders the note form template with context including\n\ + \ the form and customer." + source: inventory.views.add_note_to_customer + type: view_function + add_note_to_lead: + description: "Adds a note to a specific lead. This view is accessible only to\ + \ authenticated\nusers. The function handles the POST request to create a new\ + \ note associated\nwith a lead. Upon successful submission, the note is linked\ + \ to the provided lead,\nand the user is redirected to the lead's detail page.\ + \ If the request method is\nnot POST, it initializes an empty form to add a\ + \ note.\n\n:param request: The HTTP request object\n:type request: HttpRequest\n\ + :param pk: The primary key of the lead to which the note will be added\n:type\ + \ pk: int\n:return: HTTP response object. Redirects to the lead detail page\ + \ on successful\n note creation or renders the note form template for GET\ + \ or invalid POST requests.\n:rtype: HttpResponse" + source: inventory.views.add_note_to_lead + type: view_function + add_note_to_opportunity: + description: 'Add a note to a specific opportunity identified by its primary key. + + + This function handles the addition of a note to an existing opportunity + + by processing a POST request that includes the note content. If the note + + content is missing, an error message will be displayed. If the operation + + is successful, the note is saved, and a success message is returned. + + + :param request: The HTTP request object representing the client''s request. + + :param pk: The primary key of the Opportunity to which the note is to be added. + + :type pk: int + + :return: A redirect response to the detailed view of the opportunity.' + source: inventory.views.add_note_to_opportunity + type: view_function + apply_search_filters: + description: "Apply search filters to a queryset based on a query string.\n\n\ + This function filters the provided queryset by applying a Q object for all\n\ + CharField, TextField, or EmailField fields in the model. It checks if the\n\ + query exists within these fields using case-insensitive containment (icontains).\n\ + If the query string is empty or None, the original queryset is returned\nwithout\ + \ any modifications.\n\n:param queryset: The initial queryset to apply the search\ + \ filters on.\n:param query: The search string to match against the model fields.\ + \ If None\n or an empty string, no filtering is applied.\n:return: A filtered\ + \ queryset that satisfies the search condition." + source: inventory.views.apply_search_filters + type: view_function + assign_car_makes: + description: "Assigns car makes to a dealer.\n\nThis function handles both the\ + \ display and processing of a form that allows\na dealer to assign or modify\ + \ their associated car makes. If the request\nmethod is POST, it validates and\ + \ saves the submitted form data. If the\nmethod is not POST, it displays a form\ + \ prefilled with the existing car makes\nassociated with the dealer.\n\n:param\ + \ request: The HTTP request object containing information about the\n current\ + \ request.\n:type request: HttpRequest\n:return: A rendered HTML response for\ + \ GET requests, or a redirect to the\n dealer detail page after successful\ + \ form submission.\n:rtype: HttpResponse" + source: inventory.views.assign_car_makes + type: view_function + bank_account_delete: + description: "Delete a bank account entry from the database.\n\nThis view handles\ + \ the deletion of a bank account record specified by its\nprimary key (pk).\ + \ It renders a deletion confirmation page and processes the\ndeletion if the\ + \ request method is POST. Upon successful deletion, the user is\nredirected\ + \ to the list of bank accounts and a success message is displayed.\n\n:param\ + \ request: The HTTP request object representing the client's request.\n It\ + \ contains data such as request type (GET or POST) and session\n information.\n\ + :type request: HttpRequest\n:param pk: The primary key of the bank account model\ + \ instance to be deleted.\n:type pk: int\n:return: Returns an HttpResponse object.\ + \ This can be an HTTP redirect to the\n bank account list page upon successful\ + \ deletion, or an HTML response\n rendering the confirmation template if\ + \ accessed via GET.\n:rtype: HttpResponse" + source: inventory.views.bank_account_delete + type: view_function + bill_create: + description: "Handles creation of a bill in the system, including the validation\ + \ of input data,\ncreation of transactions associated with the bill, and rendering\ + \ of the appropriate\nresponse or form. Ensures the user creating the bill has\ + \ the necessary permissions and\ncorrect input parameters for successful bill\ + \ creation and itemization.\n\n:param request: Django HttpRequest object containing\ + \ metadata and data of the HTTP request.\n:type request: HttpRequest\n:return:\ + \ JsonResponse with success/error information if the request is processed,\n\ + \ or HttpResponse rendering the form for bill creation.\n:rtype: JsonResponse\ + \ or HttpResponse" + source: inventory.views.bill_create + type: view_function + bill_mark_as_approved: + description: "Marks a bill as approved for the given bill ID (primary key) if\ + \ it is not\nalready approved. This action can only be completed by an authorized\ + \ user\nwith the corresponding permissions. Once the bill is approved, it is\ + \ saved\nand a success message is displayed. If the bill is already approved,\ + \ an\nerror message is shown instead.\n\n:param request: HttpRequest object\ + \ representing the current request.\n:param pk: Primary key of the bill to be\ + \ marked as approved.\n:return: HttpResponseRedirect to the bill detail page\ + \ after the operation is\n completed or an error/success message is\ + \ set." + source: inventory.views.bill_mark_as_approved + type: view_function + bill_mark_as_paid: + description: 'Marks a bill as paid if certain conditions are met and updates the + ledger accordingly. + + + This function is used to mark a specific bill as paid. It verifies whether the + bill + + is already paid or if the amount paid matches the amount due. If the conditions + are + + met, it updates the bill''s status, locks the journal entries in the associated + ledger, + + posts them, and saves the changes. Appropriate success or error messages are + displayed + + to the user, and the user is redirected to the bill details page. + + + :param request: The HTTP request object containing details about the request + made by the user. + + :type request: HttpRequest + + :param pk: The primary key of the bill to be marked as paid. + + :type pk: int + + :return: A redirect response to the bill details page. + + :rtype: HttpResponseRedirect' + source: inventory.views.bill_mark_as_paid + type: view_function + car_history: + description: "Fetch and display the history of activities related to a specific\ + \ car.\n\nThis view retrieves a car object based on its primary key (pk) and\ + \ gathers\nall related activity records where the content type corresponds to\ + \ the car\nmodel. The retrieved data is then rendered into a specified HTML\ + \ template\nfor presentation.\n\n:param request: The HTTP request object that\ + \ contains metadata about the\n request made by the client.\n:type request:\ + \ HttpRequest\n:param pk: The primary key of the car object to retrieve.\n:type\ + \ pk: int\n:return: An HTTP response with the rendered car history HTML template,\n\ + \ including the car and its associated activities as context data.\n:rtype:\ + \ HttpResponse" + source: inventory.views.car_history + type: view_function + car_transfer_accept_reject: + description: "Handles the acceptance or rejection of a car transfer request. Based\ + \ on the\n`status` parameter obtained from the query string, the function updates\ + \ the\ntransfer status to either 'accept' or 'reject'. If the transfer is accepted,\ + \ it\ninitiates the car transfer process. Appropriate notifications are sent,\ + \ and\nactivity records are created for both acceptance and rejection actions.\n\ + \n:param request: The HTTP request object which contains metadata about\n \ + \ the request made by the user, including session and user information.\n:param\ + \ car_pk: The primary key of the car to be transferred.\n:param transfer_pk:\ + \ The primary key of the car transfer request to be processed.\n:return: An\ + \ HTTP redirect response to the 'inventory_stats' view." + source: inventory.views.car_transfer_accept_reject + type: view_function + car_transfer_approve: + description: 'Approves or cancels a car transfer request based on the action parameter. + This view + + handles the workflow of updating the transfer status and notifying the involved + parties + + accordingly. If the transfer is canceled, it reverts the car status to "available" + and + + deactivates the transfer record. If approved, it notifies the recipient dealer + and allows + + the request to proceed for further actions. + + + :param request: The HTTP request object containing metadata and the action parameter. + + :param car_pk: Primary key of the car involved in the transfer. + + :param transfer_pk: Primary key of the transfer request to approve or cancel. + + :return: An HTTP response redirecting to the car detail page of the specified + car.' + source: inventory.views.car_transfer_approve + type: view_function + create_estimate: + description: "Creates a new estimate based on the provided data and saves it.\ + \ This function processes\na POST request and expects a JSON payload containing\ + \ details of the estimate such as\ntitle, customer, terms, items, and quantities.\ + \ It validates the input data, ensures\navailability of stocks, and updates\ + \ or creates the corresponding estimate in the database.\n\nIf `pk` is provided,\ + \ it links the created estimate with an existing opportunity. It handles\nthe\ + \ reservation of cars and updates the stock information accordingly.\n\n:param\ + \ request: The HttpRequest object containing user-specific data and state.\n\ + :type request: HttpRequest\n:param pk: An optional primary key of the existing\ + \ opportunity to associate with\n the created estimate.\n:type pk:\ + \ int, optional\n:return: A JsonResponse object with status and either the created\ + \ quotation URL\n or an error message. If the request method is not\ + \ POST, it renders the\n estimate creation form.\n:rtype: JsonResponse\ + \ or HttpResponse" + source: inventory.views.create_estimate + type: view_function + create_sale_order: + description: "Creates a sale order for a given estimate and updates associated\ + \ item and car data.\n\nThis view is responsible for handling the submission\ + \ of a sale order form linked to\na specific estimate. It ensures that the estimate\ + \ is approved if not already, updates\nthe status of the related car items as\ + \ sold, and redirects to the estimate's detailed\nview upon successful creation\ + \ of the sale order. If the request method is not POST, it\nrenders the form\ + \ for the user to input sale order details, along with other contextual\ninformation\ + \ like estimate data and car finance details.\n\n:param request: HTTP request\ + \ object.\n:type request: HttpRequest\n:param pk: Primary key of the estimate\ + \ to create a sale order for.\n:type pk: int\n:return: An HTTP response rendering\ + \ the sale order form if the method is GET or invalid\n POST data, or redirects\ + \ to the estimate detail view upon successful creation.\n:rtype: HttpResponse" + source: inventory.views.create_sale_order + type: view_function + csrf_exempt: + description: Mark a view function as being exempt from the CSRF view protection. + source: rest_framework.views.csrf_exempt + type: view_function + custom_bad_request_view: + description: 'Handles custom bad request responses by rendering a specified HTML + + template for 400 status errors. + + + :return: Rendered HTTP response rendering the error page. + + :rtype: HttpResponse' + source: inventory.views.custom_bad_request_view + type: view_function + custom_error_view: + description: 'Handles rendering the custom error page for HTTP 500 errors. + + + This function is called when an unhandled exception occurs in the application. + It renders + + a predefined template for server errors, providing a consistent error page layout + + to the users. + + + :param request: The HTTP request instance which triggered the error. + + :type request: HttpRequest + + :param exception: The exception that caused the error to trigger. Defaults to + None. + + :type exception: Exception, optional + + :return: An HttpResponse object representing the rendered error page. + + :rtype: HttpResponse' + source: inventory.views.custom_error_view + type: view_function + custom_page_not_found_view: + description: 'Custom handler for 404 errors that renders a custom HTML page + + upon encountering a Page Not Found error. + + + :param request: The HttpRequest object associated with the request. + + :type request: HttpRequest + + :param exception: The exception that triggered the 404 error, if any. + + :type exception: Exception, optional + + :return: An HttpResponse object rendering the "errors/404.html" template. + + :rtype: HttpResponse' + source: inventory.views.custom_page_not_found_view + type: view_function + custom_permission_denied_view: + description: "Handles the custom view for 403 Forbidden permission denied errors.\ + \ This\nfunction renders a custom template for the error page when users are\ + \ denied\npermission to access a particular resource or view.\n\n:param request:\ + \ Django HttpRequest object containing metadata about the request.\n:type request:\ + \ HttpRequest\n:param exception: Optional exception that caused the 403 error.\n\ + \ Defaults to None.\n:type exception: Exception | None\n:return:\ + \ HttpResponse object rendering the 403.html error page.\n:rtype: HttpResponse" + source: inventory.views.custom_permission_denied_view + type: view_function + dealer_signup: + description: "Handles the dealer signup wizard process, including forms validation,\ + \ user and group\ncreation, permissions assignment, and dealer data storage.\ + \ This view supports GET\nrequests for rendering the signup wizard page, and\ + \ POST requests for processing\nsubmitted data. The function also differentiates\ + \ between requests sent with the\n\"Hx-Request\" header for partial form validations\ + \ in the wizard.\n\n:param request: The HTTP request object representing the\ + \ client request. It contains\n metadata about the request and the POST data\ + \ for creating the dealer.\n:type request: django.http.HttpRequest\n:param args:\ + \ Optional positional arguments passed to the view during the call.\n:type args:\ + \ tuple\n:param kwargs: Optional keyword arguments passed to the view during\ + \ the call.\n:type kwargs: dict\n:return: A rendered signup wizard page or a\ + \ JSON response indicating operation success\n or failure.\n:rtype: Union[django.http.HttpResponse,\ + \ django.http.JsonResponse]" + source: inventory.views.dealer_signup + type: view_function + decode: + description: "Decodes datamatrix barcodes in `image`.\n\nArgs:\n image: `numpy.ndarray`,\ + \ `PIL.Image` or tuple (pixels, width, height)\n symbols: iter(ZBarSymbol)\ + \ the symbol types to decode; if `None`, uses\n `zbar`'s default behaviour,\ + \ which is to decode all symbol types.\n\nReturns:\n :obj:`list` of :obj:`Decoded`:\ + \ The values decoded from barcodes." + source: inventory.views.decode + type: view_function + decodevin: + description: "Decodes a Vehicle Identification Number (VIN) using multiple decoding\ + \ functions\nand returns the decoded result. This function attempts to decode\ + \ the VIN using\nthree different functions in sequence and returns the first\ + \ successful result.\nIf none of the decoding functions produce a valid result,\ + \ the function returns None.\n\n:param vin: The Vehicle Identification Number\ + \ (VIN) to be decoded.\n:type vin: str\n:return: The decoded result if any decoding\ + \ function is successful, or None if\n all decoding attempts fail.\n\ + :rtype: dict | None" + source: api.views.decodevin + type: view_function + delete_customer: + description: 'Deletes a customer from the system and deactivates the corresponding + user account. + + + This function retrieves a customer object based on the primary key (pk), + + sets their active status to False, and deactivates the linked user account + + (using the email associated with the customer). After saving these changes, + + it displays a success message to the user and redirects to the customer list + page. + + + :param request: A HttpRequest object containing metadata about the request. + + :type request: HttpRequest + + :param pk: Primary key of the customer to be deleted. + + :type pk: int + + :return: A redirect response to the customer list page. + + :rtype: HttpResponseRedirect' + source: inventory.views.delete_customer + type: view_function + delete_note: + description: 'Deletes a specific note created by the currently logged-in user + and redirects + + to the lead detail page. If the note does not exist or the user is not the creator, + + a 404 error will be raised. A success message is displayed upon successful deletion + + of the note. + + + :param request: The HTTP request object associated with the current request. + + :type request: HttpRequest + + :param pk: The primary key of the note to be deleted. + + :type pk: int + + :return: An HTTP redirection to the lead detail page of the corresponding note''s + lead. + + :rtype: HttpResponseRedirect' + source: inventory.views.delete_note + type: view_function + delete_opportunity: + description: 'Deletes an opportunity object from the database and redirects to + the opportunity + + list view. If the opportunity with the specified primary key is not found, a + 404 + + error will be raised. Displays a success message after deletion. + + + :param request: The HTTP request object containing metadata about the request. + + :type request: HttpRequest + + :param pk: The primary key of the opportunity object to be deleted. + + :type pk: int + + :return: An HTTP response redirecting to the opportunity list view. + + :rtype: HttpResponse' + source: inventory.views.delete_opportunity + type: view_function + delete_vendor: + description: 'Deletes an existing vendor record from the database. + + + This function allows users with valid authentication to delete a specified + + vendor object by its primary key. Upon successful deletion, a success message + + is displayed, and the user is redirected to the vendor list page. + + + :param request: HttpRequest object containing metadata about the request. + + :type request: HttpRequest + + :param pk: Primary key of the vendor object to be deleted. + + :type pk: int + + :return: HttpResponseRedirect object for redirecting to the vendor list page. + + :rtype: HttpResponseRedirect' + source: inventory.views.delete_vendor + type: view_function + estimate_mark_as: + description: 'Marks an estimate with a specified status based on the requested + action and + + permissions. The marking possibilities include review, approval, rejection, + + completion, and cancellation. The function validates whether the estimate + + can transition to the desired status before updating it. It also handles + + notifications and updates related entities if required, such as car status + + changes upon cancellation. + + + :param request: The HTTP request object containing metadata about the request. + + :type request: HttpRequest + + :param pk: The primary key of the estimate to be marked. + + :type pk: int + + :return: A redirect response to the estimate detail view. + + :rtype: HttpResponseRedirect' + source: inventory.views.estimate_mark_as + type: view_function + get_car_finance_data: + description: "Fetches car finance data from the provided model instance and calculates\n\ + various related details including total prices, VAT, discounts, and\nadditional\ + \ services information.\n\n:param model: The model instance that contains car\ + \ and finance-related\n transaction data.\n:type model: Model\n:return: A\ + \ dictionary containing detailed information about the car finance\n transactions,\ + \ including individual car details, total quantity, calculated\n totals for\ + \ prices, VAT, discounts, additional services, and the VAT rate.\n:rtype: dict" + source: inventory.views.get_car_finance_data + type: view_function + get_item_transactions: + description: 'Extracts and compiles relevant transaction details from a list of + transactions, + + including information about cars, financing, estimates, and invoices. The extracted + + details for each transaction are stored as dictionaries in a list. + + + :param txs: A list of transaction objects from which information is extracted. + + :type txs: list + + :return: A list of dictionaries, each containing extracted transaction details. + + :rtype: list' + source: inventory.views.get_item_transactions + type: view_function + get_localdate: + description: "Fetches the current local date, optionally considering time zone\ + \ settings.\n\nThis function retrieves the current local date. If the global\ + \ settings indicate\nthe use of time zones (`USE_TZ` is True), the date is determined\ + \ based on the\nlocal time zone. Otherwise, the date is based on the system's\ + \ local time without\nany time zone consideration.\n\nReturns\n-------\ndate\n\ + \ The current local date, adjusted for the time zone setting if applicable." + source: django_ledger.views.get_localdate + type: view_function + get_make: + description: 'Fetches a car make object from the database based on the provided + item. The function + + first attempts an exact case-insensitive match on the full string. If no match + is found, + + it splits the input string by spaces and checks for a match with each fragment, + stopping + + as soon as a match is found. + + + :param item: A string representing the name or a part of the name of the car + make to search. + + :type item: str + + :return: The first matching CarMake object found, or None if no match is found. + + :rtype: CarMake or None' + source: inventory.views.get_make + type: view_function + get_model: + description: "Searches for a car model associated with a specific make by performing\ + \ an\nexact case-insensitive match. If no match is found, it attempts to match\n\ + based on individual words split from the input item.\n\n:param item: A string\ + \ representing the name of the car model to search for.\n:param make: An object\ + \ representing the car manufacturer, which contains the\n associated car\ + \ models.\n:return: Returns the first car model object that matches the search\ + \ criteria,\n or None if no match is found." + source: inventory.views.get_model + type: view_function + get_object_or_404: + description: 'Use get() to return an object, or raise an Http404 exception if + the object + + does not exist. + + + klass may be a Model, Manager, or QuerySet object. All other passed + + arguments and keyword arguments are used in the get() query. + + + Like with QuerySet.get(), MultipleObjectsReturned is raised if more than + + one object is found.' + source: plans.views.get_object_or_404 + type: view_function + get_user_type: + description: "Determine the type of user based on the given request object.\n\n\ + This function identifies the type of user from the provided request. It\nchecks\ + \ if the user is a dealer, staff member, or neither based on the\nattributes\ + \ of the request object, returning the appropriate associated\nuser type or\ + \ `None` if the determination cannot be made.\n\n:param request: The HTTP request\ + \ object containing user and role\n information.\n:type request: Any\n:return:\ + \ A dealer object if the user is a dealer, a dealer object\n associated with\ + \ the staff member if the user is a staff member, or\n None if the user type\ + \ cannot be identified.\n:rtype: Optional[Any]" + source: api.views.get_user_type + type: view_function + inventory_stats_view: + description: "Handle the inventory stats view for a dealer, displaying detailed\ + \ information\nabout the cars, including counts grouped by make, model, and\ + \ trim.\n\nThe function fetches all cars associated with the authenticated dealer,\ + \ calculates\nthe inventory statistics (e.g., total cars, reserved cars, and\ + \ cars categorized\nby make, model, and trim levels), and prepares the data\ + \ to be rendered in a\ntemplate.\n\n:param request: The HTTP request object\ + \ from the client.\n:type request: HttpRequest\n:return: An HTTP response containing\ + \ structured inventory data rendered in the\n \"inventory/inventory_stats.html\"\ + \ template.\n:rtype: HttpResponse" + source: inventory.views.inventory_stats_view + type: view_function + invoice_create: + description: "Handles the creation of a new invoice associated with a given estimate.\ + \ It validates\nthe submitted data through a form, processes the invoice, updates\ + \ related models, and\nfinalizes the estimate. If successful, redirects to the\ + \ detailed view of the created\ninvoice. If the submitted data is invalid or\ + \ the request is not a POST request, renders\nthe invoice creation form.\n\n\ + :param request: The HTTP request object.\n:type request: HttpRequest\n:param\ + \ pk: The primary key of the estimate associated with the invoice.\n:type pk:\ + \ int\n:return: An HTTP response. Redirects to the \"invoice_detail\" view upon\ + \ successful invoice\n creation or renders the invoice creation form template\ + \ otherwise.\n:rtype: HttpResponse" + source: inventory.views.invoice_create + type: view_function + invoice_mark_as: + description: 'Marks an invoice as approved if it meets the required conditions. + + + This view is responsible for marking an invoice as approved based on the provided + + `mark` parameter. If the `mark` parameter is specified as "accept" and the invoice + + is eligible for approval, it gets approved and saved. Otherwise, an error message + + is displayed. The function requires the user to be logged in and to have the + + appropriate permission to change the InvoiceModel. + + + :param request: The HTTP request object containing metadata about the request. + + :type request: django.http.HttpRequest + + :param pk: The primary key of the invoice to be processed. + + :type pk: int + + :return: An HTTP redirect response to the invoice detail page after processing. + + :rtype: django.http.HttpResponse' + source: inventory.views.invoice_mark_as + type: view_function + lead_convert: + description: 'Converts a lead into a customer and creates a corresponding opportunity. + + + The function ensures that leads are not converted to customers more than once. + + If the lead has already been converted, an error is displayed. Otherwise, + + the lead is converted into a customer and a new opportunity is created. + + Upon successful conversion, the user is redirected to the lead list view + + and a success message is displayed. + + + :param request: The HTTP request object representing the user''s request. + + :type request: HttpRequest + + :param pk: The primary key of the lead to be converted. + + :type pk: int + + :return: An HTTP response redirecting to the lead list view. + + :rtype: HttpResponse' + source: inventory.views.lead_convert + type: view_function + lead_create: + description: 'Handles the creation of a new lead in the inventory system. + + + This function manages the rendering and processing of a form for creating a + new + + lead. It filters options for car models in the form based on the selected car + + make. For POST requests, it validates and processes the submitted form data + + and creates the lead if valid. It also creates a corresponding customer in the + + ledger system if one does not exist for the provided email. + + + :param request: The HTTP request object containing request data. + + :type request: HttpRequest + + + :return: An HTTP response object rendering the lead creation form or redirecting + to the lead list page upon success. + + :rtype: HttpResponse' + source: inventory.views.lead_create + type: view_function + lead_transfer: + description: 'Handles the transfer of a lead to a different staff member. This + view is accessible + + only to authenticated users with the ''inventory.change_lead'' permission. If + the + + request method is POST and the form data is valid, the lead''s staff is updated + + accordingly, saved, and a success message is displayed. Otherwise, an error + message + + is shown. In both cases, the user is redirected to the lead listing page. + + + :param request: The HTTP request object. + + :param pk: The primary key of the lead to be transferred. + + :return: An HTTP redirect response to the lead list view.' + source: inventory.views.lead_transfer + type: view_function + ledger_lock_all_journals: + description: 'Locks all journals associated with a specific ledger. If the ledger + is already locked, + + it will notify the user through an error message. Otherwise, it initiates the + locking of + + related journal entries, locks the ledger itself, and saves the changes to the + database. + + After the operation, it redirects the user to the journal entry list associated + with the + + ledger. + + + :param request: HttpRequest object representing the current request. + + :type request: HttpRequest + + :param entity_slug: The slug identifier of the entity. + + :type entity_slug: str + + :param pk: The primary key of the ledger to be locked. + + :type pk: int + + :return: HttpResponse redirecting to the journal entry list page of the locked + ledger. + + :rtype: HttpResponse' + source: inventory.views.ledger_lock_all_journals + type: view_function + ledger_post_all_journals: + description: 'Posts all journal entries associated with a ledger. This function + updates the ledger''s + + state to reflect that its journal entries have been posted. If the ledger is + already + + posted, an error message is displayed and the user is redirected. + + + :param request: The HTTP request object used for processing the action. + + :type request: HttpRequest + + :param entity_slug: A string representing the specific entity slug for the ledger + context. + + :type entity_slug: str + + :param pk: The primary key of the ledger to be posted. + + :type pk: int + + :return: A redirect to the journal entry list view for the specified ledger. + + :rtype: HttpResponseRedirect' + source: inventory.views.ledger_post_all_journals + type: view_function + ledger_unlock_all_journals: + description: 'Unlocks all journal entries associated with a specific ledger. This + function first checks if the + + ledger is locked. If it is already unlocked, it shows an error message and redirects + the user + + to the journal entry list page. If the ledger is locked, it unlocks the ledger, + saves changes, + + and iterates through all the locked journal entries within the ledger to unlock + them as well. + + Afterward, it redirects the user to the journal entry list page. + + + :param request: The HTTP request object containing user session and request + metadata. + + :type request: HttpRequest + + :param entity_slug: A unique string slug representing the specific entity or + organization context. + + :type entity_slug: str + + :param pk: The primary key of the ledger record to be unlocked. + + :type pk: int + + :return: A redirection to the journal entry list page for the specified ledger. + + :rtype: HttpResponseRedirect' + source: inventory.views.ledger_unlock_all_journals + type: view_function + ledger_unpost_all_journals: + description: "Unposts all journal entries for a specified ledger and marks the\ + \ ledger as unposted.\n\nThis function identifies a ledger by its primary key\ + \ (pk) and checks if it is\nalready posted. If the ledger is not posted, an\ + \ error message is displayed.\nIf it is posted, the function iterates through\ + \ its posted journal entries,\nmarks them as unposted, and saves the changes.\ + \ Finally, it marks the ledger itself\nas unposted and saves it.\n\n:param request:\ + \ The HTTP request object from the client.\n:type request: HttpRequest\n:param\ + \ entity_slug: A slug identifying the entity associated with the ledger.\n:type\ + \ entity_slug: str\n:param pk: The primary key of the ledger whose journal entries\ + \ are being unposted.\n:type pk: int\n:return: An HTTP redirect response object\ + \ directing the client to the journal entry list\n page for the specified\ + \ ledger.\n:rtype: HttpResponseRedirect" + source: inventory.views.ledger_unpost_all_journals + type: view_function + manage_reservation: + description: "Handles the management of a car reservation, providing options to\ + \ renew or\ncancel an existing reservation associated with the logged-in user.\n\ + \nRenewing a reservation extends the reservation period by an additional\n24\ + \ hours. Canceling a reservation deletes it and updates the car's status\nto\ + \ AVAILABLE. All actions require a valid reservation and are performed\nbased\ + \ on the current user's authentication and request type.\n\n:param request:\ + \ Django HttpRequest object representing the client's request.\n:type request:\ + \ HttpRequest\n:param reservation_id: The unique identifier of the car reservation\ + \ to manage.\n:type reservation_id: int\n:return: On POST requests, returns\ + \ an HTTP redirect or JSON response\n based on the action performed. On other\ + \ request methods,\n returns a JSON response with an error message.\n:rtype:\ + \ JsonResponse or HttpResponseRedirect" + source: inventory.views.manage_reservation + type: view_function + opportunity_update_status: + description: "Update the status and/or stage of a specific Opportunity instance.\ + \ This is a\nview function, which is generally tied to a URL endpoint in a Django\ + \ application.\nThe function is accessible only to authenticated users due to\ + \ the\n`@login_required` decorator.\n\nThe function retrieves the `Opportunity`\ + \ instance based on the primary key\nin the URL, updates the status or stage\ + \ of the instance based on query\nparameters in the request, saves the changes,\ + \ and then redirects to the\ndetail view of the `Opportunity`. Additionally,\ + \ a success message is displayed,\nand a custom header (`HX-Refresh`) is added\ + \ to the response.\n\n:param request: The HTTP request object containing details\ + \ of the user's\n request, such as query parameters for status and stage.\ + \ This is a\n mandatory parameter provided by Django's view function framework.\n\ + :type request: HttpRequest\n:param pk: The primary key of the `Opportunity`\ + \ instance to be updated.\n:type pk: int\n\n:return: An HTTP response object\ + \ that redirects to the updated Opportunity's\n detail page, with a success\ + \ message and the \"HX-Refresh\" header to trigger\n frontend behavior.\n\ + :rtype: HttpResponse" + source: inventory.views.opportunity_update_status + type: view_function + payment_mark_as_paid: + description: "Marks an invoice as paid if it meets the conditions of being fully\ + \ paid and eligible\nfor payment. Also ensures that related ledger journal entries\ + \ are locked and posted\nwhen the payment is marked successfully.\n\nThis function\ + \ is protected with both `login_required` and\n`permission_required` decorators,\ + \ ensuring that only logged-in users with\nappropriate permissions can execute\ + \ it.\n\n:param request: HttpRequest object containing metadata about the request.\n\ + :type request: HttpRequest\n:param pk: Primary key of the invoice to mark as\ + \ paid.\n:type pk: int\n:return: Redirect response to the invoice detail page.\n\ + :rtype: HttpResponseRedirect\n:raises: In case of an exception during the process,\ + \ an error message is\n displayed to the user through Django's messaging\ + \ framework." + source: inventory.views.payment_mark_as_paid + type: view_function + permission_required: + description: 'Decorator for views that checks whether a user has a particular + permission + + enabled, redirecting to the log-in page if necessary. + + If the raise_exception parameter is given the PermissionDenied exception + + is raised.' + source: inventory.views.permission_required + type: view_function + preview_sale_order: + description: 'Handles rendering of the sale order preview page for a specific + estimate. + + + This view retrieves an `EstimateModel` object based on the provided primary + + key (`pk`), fetches related car finance data, and renders the preview of the + + sale order associated with the given estimate. + + + :param request: The HTTP request object + + :type request: HttpRequest + + :param pk: The primary key of the `EstimateModel` to retrieve + + :type pk: int + + :return: HTTP response containing the rendered sale order preview page + + :rtype: HttpResponse' + source: inventory.views.preview_sale_order + type: view_function + redirect: + description: "Return an HttpResponseRedirect to the appropriate URL for the arguments\n\ + passed.\n\nThe arguments could be:\n\n * A model: the model's `get_absolute_url()`\ + \ function will be called.\n\n * A view name, possibly with arguments: `urls.reverse()`\ + \ will be used\n to reverse-resolve the name.\n\n * A URL, which will\ + \ be used as-is for the redirect location.\n\nIssues a temporary redirect by\ + \ default. Set permanent=True to issue a\npermanent redirect. Set preserve_request=True\ + \ to instruct the user agent\nto preserve the original HTTP method and body\ + \ when following the redirect." + source: appointment.views.redirect + type: view_function + render: + description: 'Return an HttpResponse whose content is filled with the result of + calling + + django.template.loader.render_to_string() with the passed arguments.' + source: django_pdf_actions.views.render + type: view_function + reserve_car: + description: 'Reserve a car for a user for 24 hours and update its status to reserved. + + + The function creates a reservation record for the specified car, sets the + + reservation expiration time, updates the car''s status, and notifies the user + + about the operation outcome through success or error messages. It then redirects + + to the car detail page. + + + :param car: The car object to be reserved. + + :param request: The HTTP request object containing the user making the reservation. + + :return: Redirection to the car''s detail page.' + source: inventory.views.reserve_car + type: view_function + reserve_car_view: + description: 'Handles car reservation requests. This view requires the user to + be logged in + + and processes only POST requests. When invoked, it checks if the specified car + + is already reserved. If not, it proceeds to reserve the car for the user and + + sends an appropriate response. If the car is already reserved or if the request + + method is invalid, it provides corresponding error messages or responses. + + + :param request: The HTTP request object. + + :type request: HttpRequest + + :param car_id: The unique identifier of the car to be reserved. + + :type car_id: int + + :return: A response indicating the result of the reservation process. + + :rtype: HttpResponse or JsonResponse' + source: inventory.views.reserve_car_view + type: view_function + sales_list_view: + description: "Handles the retrieval and presentation of a paginated list of item\ + \ transactions for\nsales, specific to the logged-in user's entity. Requires\ + \ the user to have appropriate\npermissions to view the list.\n\n:param request:\ + \ The HTTP request object containing metadata about the request,\n such as\ + \ HTTP method, user credentials, and sent data.\n:type request: HttpRequest\n\ + \n:return: An HTTP response with the rendered sales list page containing the\ + \ paginated\n item transactions specific to the user's entity.\n:rtype: HttpResponse" + source: inventory.views.sales_list_view + type: view_function + schedule_cancel: + description: 'Cancel a schedule by updating its status to "Canceled". The function + is protected + + by a login requirement, ensuring only authenticated users can execute it. It + + retrieves the schedule object by its primary key, updates its status, saves + the + + changes, and returns an HTTP response with status code 200. + + + :param request: The HTTP request object representing the user''s request. + + :type request: HttpRequest + + :param pk: The primary key of the schedule to be canceled. + + :type pk: int + + :return: An HTTP response object with a 200 status code upon successful execution. + + :rtype: HttpResponse' + source: inventory.views.schedule_cancel + type: view_function + schedule_lead: + description: "Handles the scheduling of a lead for an appointment.\n\nThis function\ + \ ensures that only staff members with the appropriate permissions\ncan schedule\ + \ leads. If the user is not a staff member or does not have the\nrequired inventory\ + \ permissions, they are redirected with an appropriate error\nmessage. The function\ + \ creates an appointment request and an associated appointment\nrecord upon\ + \ successful submission of the scheduling form.\n\n:param request: The HTTP\ + \ request object containing metadata about the request.\n:type request: HttpRequest\n\ + :param pk: The primary key of the lead to be scheduled.\n:type pk: int\n:return:\ + \ A rendered template or a redirection to another view based on the request\n\ + \ method and validity of the form submission.\n:rtype: HttpResponse" + source: inventory.views.schedule_lead + type: view_function + send_email_view: + description: 'View function to send an email for an estimate. This function allows + authenticated and + + authorized users to send an email containing the estimate details to the customer. + + The email includes a link to preview the estimate and a message template in + multiple + + languages. If the estimate does not have any associated items, the user will + receive + + an error message. Upon successfully sending the email, the estimate is marked + as reviewed. + + + :param request: The HttpRequest object containing metadata about the request. + + :type request: HttpRequest + + :param pk: The primary key of the estimate to be sent via email. + + :type pk: int + + :return: An HttpResponseRedirect to the estimate detail view. + + :rtype: HttpResponseRedirect + + :raises Http404: If the estimate with the given primary key does not exist.' + source: inventory.views.send_email_view + type: view_function + send_lead_email: + description: "Handles sending emails related to a lead. Supports creating drafts,\ + \ sending emails, and rendering\nan email composition page. Changes on the lead\ + \ or email objects, such as marking a lead as contacted\nor an email as sent,\ + \ are recorded in associated activity logs.\n\n:param request: The HTTP request\ + \ object. This contains user information, method type, and data such as\n \ + \ GET or POST payload used to draft or send the email appropriately.\n\ + \ Type: HttpRequest\n:param pk: The primary key of the lead to\ + \ which the email action is associated. It's used to retrieve\n the\ + \ lead object from the database.\n Type: int\n:param email_pk: Optional\ + \ parameter representing the primary key of an email template. When provided,\n\ + \ the respective email content is pre-populated into the email\ + \ composition form.\n Defaults to None.\n Type:\ + \ Optional[int]\n:return: When successfully sending an email, redirects the\ + \ user to the lead list page. On draft mode\n or email composition rendering,\ + \ a response object is returned to render the respective page.\n Type:\ + \ HttpResponse" + source: inventory.views.send_lead_email + type: view_function + set_bill_payment: + description: 'Sets the payment for a given bill by creating journal entries for + the + + transaction and updating the respective accounts and the bill''s status. + + + The function handles the transaction by creating a new journal entry + + linked with the specified bill, then records debit and credit + + transactions using the entity’s cash and accounts payable accounts. + + It finally updates the bill''s payment status and persists changes. + + + :param dealer: The dealer making or receiving the payment. + + :type dealer: Any + + :param entity: The business entity involved in the payment transaction. + + :type entity: Any + + :param bill: The bill object representing the invoice to be paid. + + :type bill: Any + + :param amount: The amount to be paid for the bill. + + :type amount: Decimal + + :param payment_method: The method used to make the payment (e.g., cash, credit). + + :type payment_method: Any + + :return: None' + source: inventory.views.set_bill_payment + type: view_function + set_invoice_payment: + description: 'Processes and applies a payment for a specified invoice. This function + calculates + + finance details, handles associated account transactions, and updates the invoice + + status accordingly. + + + :param dealer: Dealer object responsible for processing the payment + + :type dealer: Dealer + + :param entity: Entity object associated with the invoice and payment + + :type entity: Entity + + :param invoice: The invoice object for which the payment is being made + + :type invoice: Invoice + + :param amount: The amount being paid towards the invoice + + :type amount: Decimal + + :param payment_method: The payment method used for the transaction + + :type payment_method: str + + :return: None' + source: inventory.views.set_invoice_payment + type: view_function + switch_language: + description: "Switches the current language context for the user based on a request\ + \ parameter, modifies the URL path\naccordingly, and updates session and cookies\ + \ with the new language preference.\n\n:param request: The HTTP request object\ + \ containing information about the user request, including\n \ + \ the desired language to switch to and the referring URL.\n \ + \ - \"GET\" dictionary is accessed to retrieve the desired language parameter.\n\ + \ - \"META\" dictionary is used to extract the referring URL\ + \ via \"HTTP_REFERER\".\n\n:return: A redirect response object pointing to the\ + \ modified URL with the updated language\n preference, if the requested\ + \ language is valid. Otherwise, redirects to the default URL." + source: inventory.views.switch_language + type: view_function + urlparse: + description: 'Parse a URL into 6 components: + + :///;?# + + + The result is a named 6-tuple with fields corresponding to the + + above. It is either a ParseResult or ParseResultBytes object, + + depending on the type of the url parameter. + + + The username, password, hostname, and port sub-components of netloc + + can also be accessed as attributes of the returned object. + + + The scheme argument provides the default value of the scheme + + component when no scheme is found in url. + + + If allow_fragments is False, no attempt is made to separate the + + fragment component from the previous component, which can be either + + path or query. + + + Note that % escapes are not expanded.' + source: inventory.views.urlparse + type: view_function + urlunparse: + description: 'Put a parsed URL back together again. This may result in a + + slightly different, but equivalent URL, if the URL that was parsed + + originally had redundant delimiters, e.g. a ? with an empty query + + (the draft states that these are equivalent).' + source: inventory.views.urlunparse + type: view_function + vendorDetailView: + description: 'Fetches and renders the detail view for a specific vendor. + + + This function retrieves a vendor object based on the primary key (pk) + + provided in the URL, ensures the user is logged in to access the + + view, and renders the vendor detail template with the vendor''s context. + + + :param request: The HTTP request object. + + :type request: HttpRequest + + :param pk: The primary key of the vendor to retrieve. + + :type pk: int + + :return: An HttpResponse object containing the rendered vendor detail page. + + :rtype: HttpResponse' + source: inventory.views.vendorDetailView + type: view_function + sync_user_email_addresses: + description: 'Keep user.email in sync with user.emailaddress_set. + + + Under some circumstances the user.email may not have ended up as + + an EmailAddress record, e.g. in the case of manually created admin + + users.' + source: allauth.account.views.sync_user_email_addresses + type: view_function + escape: + description: 'Return the given text with ampersands, quotes and angle brackets + encoded + + for use in HTML. + + + Always escape input, even if it''s already escaped and marked as such. + + This may result in double-escaping. If this is a concern, use + + conditional_escape() instead.' + source: debug_toolbar.views.escape + type: view_function + render_panel: + description: Render the contents of a panel + source: debug_toolbar.views.render_panel + type: view_function + render_with_toolbar_language: + description: Force any rendering within the view to use the toolbar's language. + source: debug_toolbar.views.render_with_toolbar_language + type: view_function + require_show_toolbar: + description: 'Async compatible decorator to restrict access to a view + + based on the Debug Toolbar''s visibility settings.' + source: debug_toolbar.views.require_show_toolbar + type: view_function + exception_handler: + description: 'Returns the response that should be used for any given exception. + + + By default we handle the REST framework `APIException`, and also + + Django''s built-in `Http404` and `PermissionDenied` exceptions. + + + Any unhandled exceptions may return `None`, which will cause a 500 error + + to be raised.' + source: rest_framework.views.exception_handler + type: view_function + get_view_description: + description: 'Given a view instance, return a textual description to represent + the view. + + This name is used in the browsable API, and in OPTIONS responses. + + + This function is the default for the `VIEW_DESCRIPTION_FUNCTION` setting.' + source: rest_framework.views.get_view_description + type: view_function + get_view_name: + description: 'Given a view instance, return a textual name to represent the view. + + This name is used in the browsable API, and in OPTIONS responses. + + + This function is the default for the `VIEW_NAME_FUNCTION` setting.' + source: rest_framework.views.get_view_name + type: view_function + patch_vary_headers: + description: 'Add (or update) the "Vary" header in the given HttpResponse object. + + newheaders is a list of header names that should be in "Vary". If headers + + contains an asterisk, then "Vary" header will consist of a single asterisk + + ''*''. Otherwise, existing headers in "Vary" aren''t removed.' + source: rest_framework.views.patch_vary_headers + type: view_function + smart_str: + description: 'Return a string representing ''s''. Treat bytestrings using the + ''encoding'' + + codec. + + + If strings_only is True, don''t convert (some) non-string-like objects.' + source: rest_framework.views.smart_str + type: view_function + add_message: + description: Attempt to add a message to the request using the 'messages' app. + source: django_ledger.views.add_message + type: view_function + format_html: + description: 'Similar to str.format, but pass all arguments through conditional_escape(), + + and call mark_safe() on the result. This function should be used instead + + of str.format or % interpolation to build up small HTML fragments.' + source: django_ledger.views.format_html + type: view_function + get_localtime: + description: "Retrieve the local time based on the specified timezone.\n\nDetermines\ + \ the local time depending on whether timezone support (``USE_TZ``) is\nenabled\ + \ in global settings. If timezone support is enabled, it uses the\n`localtime`\ + \ function to obtain the local time according to the provided\ntimezone. If\ + \ timezone support is disabled, it defaults to the current time\nwith respect\ + \ to the given timezone.\n\nParameters\n----------\ntz : timezone or None, optional\n\ + \ The timezone to determine the local time. If `None`, defaults to the system\n\ + \ timezone.\n\nReturns\n-------\ndatetime\n A datetime object representing\ + \ the calculated local time." + source: django_ledger.views.get_localtime + type: view_function + make_aware: + description: Make a naive datetime.datetime in a given time zone aware. + source: django_ledger.views.make_aware + type: view_function + analyze_prompt_sync: + description: "Synchronous function to analyze a prompt and return insights.\n\ + Perfect for Django views.\n\nArgs:\n prompt: Natural language query\n \ + \ **kwargs: Additional parameters for InsightRequest\n\nReturns:\n Dictionary\ + \ with query results" + source: haikalbot.views.analyze_prompt_sync + type: view_function + export_to_csv: + description: "Export data to CSV format.\n\nArgs:\n data: Data to export\n\ + \ filename: Base filename without extension\n\nReturns:\n HttpResponse:\ + \ Response with CSV file" + source: haikalbot.views.export_to_csv + type: view_function + export_to_excel: + description: "Export data to Excel format.\n\nArgs:\n data: Data to export\n\ + \ filename: Base filename without extension\n\nReturns:\n HttpResponse:\ + \ Response with Excel file" + source: haikalbot.views.export_to_excel + type: view_function + appointment_client_information: + description: 'This view function handles client information submission for an + appointment. + + + :param request: The request instance. + + :param appointment_request_id: The ID of the appointment request. + + :param id_request: The unique ID of the appointment request. + + :return: The rendered HTML page.' + source: appointment.views.appointment_client_information + type: view_function + appointment_request: + description: 'This view function handles requests to book an appointment for a + service. + + + :param request: The request instance. + + :param service_id: The ID of the service. + + :param staff_member_id: The ID of the staff member. + + :return: The rendered HTML page.' + source: appointment.views.appointment_request + type: view_function + appointment_request_submit: + description: 'This view function handles the submission of the appointment request + form. + + + :param request: The request instance. + + :return: The rendered HTML page.' + source: appointment.views.appointment_request_submit + type: view_function + check_day_off_for_staff: + description: 'Check if the given staff member is off on the given date. + + :param staff_member: The staff member to check. + + :param date: The date to check.' + source: appointment.views.check_day_off_for_staff + type: view_function + check_q_cluster: + description: 'Checks if Django Q is properly installed and configured in the Django + settings. + + If ''django_q'' is not in INSTALLED_APPS, it warns about both ''django_q'' not + being installed + + and ''Q_CLUSTER'' likely not being configured. + + If ''django_q'' is installed but ''Q_CLUSTER'' is not configured, it only warns + about ''Q_CLUSTER''. + + Returns True if configurations are correct, otherwise False.' + source: appointment.views.check_q_cluster + type: view_function + convert_str_to_date: + description: "Convert a date string to a datetime date object.\n\n:param date_str:\ + \ The date string.\n Supported formats include `%Y-%m-%d` (like\ + \ \"2023-12-31\") and `%Y/%m/%d` (like \"2023/12/31\").\n:return: The converted\ + \ `datetime.date`'s object." + source: appointment.views.convert_str_to_date + type: view_function + create_and_save_appointment: + description: 'Create and save a new appointment based on the provided appointment + request and client data. + + + :param ar: The appointment request associated with the new appointment. + + :param client_data: The data of the client making the appointment. + + :param appointment_data: Additional data for the appointment, including phone + number, address, etc. + + :param request: The request object. + + :return: The newly created appointment.' + source: appointment.views.create_and_save_appointment + type: view_function + create_appointment: + description: 'This function creates a new appointment and redirects to the payment + page or the thank-you page. + + + :param request: The request instance. + + :param appointment_request_obj: The AppointmentRequest instance. + + :param client_data: The client data. + + :param appointment_data: The appointment data. + + :return: The redirect response.' + source: appointment.views.create_appointment + type: view_function + create_payment_info_and_get_url: + description: 'Create a new payment information entry for the appointment and return + the payment URL. + + + :param appointment: The appointment to create the payment information for. + + :return: The payment URL for the appointment.' + source: appointment.views.create_payment_info_and_get_url + type: view_function + default_thank_you: + description: 'This view function handles the default thank you page. + + + :param request: The request instance. + + :param appointment_id: The ID of the appointment. + + :return: The rendered HTML page.' + source: appointment.views.default_thank_you + type: view_function + enter_verification_code: + description: 'This view function handles the submission of the email verification + code. + + + :param request: The request instance. + + :param appointment_request_id: The ID of the appointment request. + + :param id_request: The unique ID of the appointment request. + + :return: The rendered HTML page.' + source: appointment.views.enter_verification_code + type: view_function + force_str: + description: 'Similar to smart_str(), except that lazy instances are resolved + to + + strings, rather than kept as lazy objects. + + + If strings_only is True, don''t convert (some) non-string-like objects.' + source: appointment.views.force_str + type: view_function + get_appointment_data_from_session: + description: 'Get the appointment data from the session variables. + + Retrieves the phone, want_reminder, address, and additional_info stored in the + session. + + + :param request: The Django HttpRequest object. + + :return: The appointment data retrieved from the session.' + source: appointment.views.get_appointment_data_from_session + type: view_function + get_appointments_and_slots: + description: "Get appointments and available slots for a given date and service.\n\ + \nIf a service is provided, the function retrieves appointments for that service\ + \ on the given date.\nOtherwise, it retrieves all appointments for the given\ + \ date.\n\n:param date_: datetime.date, the date for which to retrieve appointments\ + \ and available slots\n:param service: Service, the service for which to retrieve\ + \ appointments\n:return: tuple, a tuple containing two elements:\n - A queryset\ + \ of appointments for the given date and service (if provided).\n - A list\ + \ of available time slots on the given date, excluding booked appointments." + source: appointment.views.get_appointments_and_slots + type: view_function + get_available_slots_ajax: + description: 'This view function handles AJAX requests to get available slots + for a selected date. + + + :param request: The request instance. + + :return: A JSON response containing available slots, selected date, an error + flag, and an optional error message.' + source: appointment.views.get_available_slots_ajax + type: view_function + get_available_slots_for_staff: + description: 'Calculate the available time slots for a given date and a staff + member. + + + :param date: The date for which to calculate the available slots + + :param staff_member: The staff member for which to calculate the available slots + + :return: A list of available time slots as strings in the format ''%I:%M %p'' + like [''10:00 AM'', ''10:30 AM'']' + source: appointment.views.get_available_slots_for_staff + type: view_function + get_current_timezone_name: + description: Return the name of the currently active time zone. + source: appointment.views.get_current_timezone_name + type: view_function + get_generic_context_with_extra: + description: Get the generic context for the admin pages with extra context. + source: appointment.views.get_generic_context_with_extra + type: view_function + get_locale: + description: 'Get the current locale based on the user''s language settings, without + the country code. + + Used in the JavaScript files. + + Can''t use the lang_country format because it is not supported. + + + :return: The current locale as a string (language code only)' + source: appointment.views.get_locale + type: view_function + get_next_available_date_ajax: + description: 'This view function handles AJAX requests to get the next available + date for a service. + + + :param request: The request instance. + + :param service_id: The ID of the service. + + :return: A JSON response containing the next available date.' + source: appointment.views.get_next_available_date_ajax + type: view_function + get_non_working_days_for_staff: + description: Return the non-working days for the given staff member or an empty + list if the staff member does not exist. + source: appointment.views.get_non_working_days_for_staff + type: view_function + get_user_by_email: + description: 'Get a user by their email address. + + + :param email: The email address of the user. + + :return: The user with the specified email address, if found; otherwise, None.' + source: appointment.views.get_user_by_email + type: view_function + get_website_name: + description: 'Get the website name from the configuration file. + + + :return: The website name' + source: appointment.views.get_website_name + type: view_function + get_weekday_num_from_date: + description: Get the number of the weekday from the given date. + source: appointment.views.get_weekday_num_from_date + type: view_function + handle_existing_email: + description: 'Handle the case where the email already exists in the database. + + + Sends a verification email to the existing user and redirects the client to + enter the verification code. + + + If the email is already in the session variables, clean the session variables + for email, phone, want_reminder, + + address, and additional_info. Then, store the current email, phone, want_reminder, + address, and additional_info + + in the session. + + + :param request: The Django HttpRequest object. + + :param client_data: The data of the client for the appointment. + + :param appointment_data: The data of the appointment. + + :param appointment_request_id: The ID of the appointment request. + + :param id_request: The unique ID for the appointment request. + + :return: The redirect response to enter the verification code.' + source: appointment.views.handle_existing_email + type: view_function + has_required_email_settings: + description: Check if all required email settings are configured and warn if any + are missing. + source: appointment.views.has_required_email_settings + type: view_function + is_working_day: + description: Check if the given day is a working day for the staff member. + source: appointment.views.is_working_day + type: view_function + json_response: + description: Return a generic JSON response. + source: appointment.views.json_response + type: view_function + login: + description: 'Persist a user id and a backend in the request. This way a user + doesn''t + + have to reauthenticate on every request. Note that data set during + + the anonymous session is retained when the user logs in.' + source: appointment.views.login + type: view_function + notify_admin_about_appointment: + description: Notify the admin and the staff member about a new appointment request. + source: appointment.views.notify_admin_about_appointment + type: view_function + notify_admin_about_reschedule: + description: Notify the admin and the staff member about a rescheduled appointment + request. + source: appointment.views.notify_admin_about_reschedule + type: view_function + redirect_to_payment_or_thank_you_page: + description: 'This function redirects to the payment page or the thank-you page + based on the configuration. + + + :param appointment: The Appointment instance. + + :return: The redirect response.' + source: appointment.views.redirect_to_payment_or_thank_you_page + type: view_function + require_ajax: + description: 'Decorator to require a request to be AJAX. + + Usage: @require_ajax' + source: appointment.views.require_ajax + type: view_function + send_reschedule_confirmation_email: + description: Send a rescheduling confirmation email to the client. + source: appointment.views.send_reschedule_confirmation_email + type: view_function + send_thank_you_email: + description: 'Send a thank-you email to the client for booking an appointment. + + + :param ar: The appointment request associated with the booking. + + :param user: The user who booked the appointment. + + :param email: The email address of the client. + + :param appointment_details: Additional details about the appointment (default + None). + + :param account_details: Additional details about the account (default None). + + :param request: The request object. + + :return: None' + source: appointment.views.send_thank_you_email + type: view_function + verify_user_and_login: + description: 'This function verifies the user''s email and logs the user in. + + + :param request: The request instance. + + :param user: The User instance. + + :param code: The verification code.' + source: appointment.views.verify_user_and_login + type: view_function + import_name: + description: import module given by str or pass the module if it is not str + source: plans.views.import_name + type: view_function + plan_validation: + description: 'Validates validator that represents quotas in a given system + + :param user: + + :param plan: + + :return:' + source: plans.views.plan_validation + type: view_function + Site: + description: Site(id, domain, name) + source: sites.models.Site + type: model_class + LogEntry: + description: LogEntry(id, action_time, user, content_type, object_id, object_repr, + action_flag, change_message) + source: admin.models.LogEntry + type: model_class + Permission: + description: "The permissions system provides a way to assign permissions to specific\n\ + users and groups of users.\n\nThe permission system is used by the Django admin\ + \ site, but may also be\nuseful in your own code. The Django admin site uses\ + \ permissions as follows:\n\n - The \"add\" permission limits the user's\ + \ ability to view the \"add\" form\n and add an object.\n - The \"change\"\ + \ permission limits a user's ability to view the change\n list, view the\ + \ \"change\" form and change an object.\n - The \"delete\" permission limits\ + \ the ability to delete an object.\n - The \"view\" permission limits the\ + \ ability to view an object.\n\nPermissions are set globally per type of object,\ + \ not per specific object\ninstance. It is possible to say \"Mary may change\ + \ news stories,\" but it's\nnot currently possible to say \"Mary may change\ + \ news stories, but only the\nones she created herself\" or \"Mary may only\ + \ change news stories that have a\ncertain status or publication date.\"\n\n\ + The permissions listed above are automatically created for each model." + source: auth.models.Permission + type: model_class + Group: + description: 'Groups are a generic way of categorizing users to apply permissions, + or + + some other label, to those users. A user can belong to any number of + + groups. + + + A user in a group automatically has all the permissions granted to that + + group. For example, if the group ''Site editors'' has the permission + + can_edit_home_page, any user in that group will have that permission. + + + Beyond permissions, groups are a convenient way to categorize users to + + apply some label, or extended functionality, to them. For example, you + + could create a group ''Special users'', and you could write code that would + + do special things to those users -- such as giving them access to a + + members-only portion of your site, or sending them members-only email + + messages.' + source: auth.models.Group + type: model_class + User: + description: 'Users within the Django authentication system are represented by + this + + model. + + + Username and password are required. Other fields are optional.' + source: auth.models.User + type: model_class + ContentType: + description: ContentType(id, app_label, model) + source: contenttypes.models.ContentType + type: model_class + Session: + description: 'Django provides full support for anonymous sessions. The session + + framework lets you store and retrieve arbitrary data on a + + per-site-visitor basis. It stores data on the server side and + + abstracts the sending and receiving of cookies. Cookies contain a + + session ID -- not the data itself. + + + The Django sessions framework is entirely cookie-based. It does + + not fall back to putting session IDs in URLs. This is an intentional + + design decision. Not only does that behavior make URLs ugly, it makes + + your site vulnerable to session-ID theft via the "Referer" header. + + + For complete documentation on using Sessions in your code, consult + + the sessions documentation that is shipped with Django (also available + + on the Django web site).' + source: sessions.models.Session + type: model_class + DealersMake: + description: 'Represents the relationship between a car dealer and a car make. + + + This model establishes a many-to-many relationship between dealers and + + car makes, allowing each dealer to be associated with multiple car makes + + and each car make to be associated with multiple dealers. It also keeps + + track of the date and time when the relationship was added. + + + :ivar dealer: The dealer associated with the car make. + + :type dealer: ForeignKey + + :ivar car_make: The car make associated with the dealer. + + :type car_make: ForeignKey + + :ivar added_at: The date and time when the relationship was created. + + :type added_at: DateTimeField' + source: inventory.models.DealersMake + type: model_class + VatRate: + description: VatRate(id, rate, is_active, created_at) + source: inventory.models.VatRate + type: model_class + CarMake: + description: CarMake(id_car_make, name, slug, arabic_name, logo, is_sa_import, + car_type) + source: inventory.models.CarMake + type: model_class + CarModel: + description: CarModel(id_car_model, id_car_make, name, arabic_name, slug) + source: inventory.models.CarModel + type: model_class + CarSerie: + description: CarSerie(id_car_serie, id_car_model, name, arabic_name, year_begin, + year_end, generation_name, slug) + source: inventory.models.CarSerie + type: model_class + CarTrim: + description: CarTrim(id_car_trim, id_car_serie, name, arabic_name, start_production_year, + end_production_year, slug) + source: inventory.models.CarTrim + type: model_class + CarEquipment: + description: CarEquipment(id_car_equipment, id_car_trim, name, arabic_name, year_begin, + slug) + source: inventory.models.CarEquipment + type: model_class + CarSpecification: + description: CarSpecification(id_car_specification, name, arabic_name, id_parent, + slug) + source: inventory.models.CarSpecification + type: model_class + CarSpecificationValue: + description: CarSpecificationValue(id_car_specification_value, id_car_trim, id_car_specification, + value, unit) + source: inventory.models.CarSpecificationValue + type: model_class + CarOption: + description: CarOption(id_car_option, name, arabic_name, id_parent, slug) + source: inventory.models.CarOption + type: model_class + CarOptionValue: + description: CarOptionValue(id_car_option_value, id_car_option, id_car_equipment, + value, unit, is_base) + source: inventory.models.CarOptionValue + type: model_class + AdditionalServices: + description: AdditionalServices(id, name, arabic_name, description, price, taxable, + uom, dealer, item) + source: inventory.models.AdditionalServices + type: model_class + Car: + description: Car(id, slug, created_at, updated_at, item_model, vin, dealer, vendor, + id_car_make, id_car_model, year, id_car_serie, id_car_trim, status, stock_type, + remarks, mileage, receiving_date, hash) + source: inventory.models.Car + type: model_class + CarTransfer: + description: CarTransfer(id, car, from_dealer, to_dealer, transfer_date, quantity, + remarks, status, is_approved, active, created_at, updated_at) + source: inventory.models.CarTransfer + type: model_class + CarReservation: + description: CarReservation(id, car, reserved_by, reserved_at, reserved_until) + source: inventory.models.CarReservation + type: model_class + CarFinance: + description: CarFinance(id, car, cost_price, selling_price, discount_amount, is_sold) + source: inventory.models.CarFinance + type: model_class + ExteriorColors: + description: ExteriorColors(id, name, arabic_name, rgb) + source: inventory.models.ExteriorColors + type: model_class + InteriorColors: + description: InteriorColors(id, name, arabic_name, rgb) + source: inventory.models.InteriorColors + type: model_class + CarColors: + description: CarColors(id, car, exterior, interior) + source: inventory.models.CarColors + type: model_class + CustomCard: + description: CustomCard(id, car, custom_number, custom_date) + source: inventory.models.CustomCard + type: model_class + CarLocation: + description: CarLocation(id, car, owner, showroom, description, created_at, updated_at) + source: inventory.models.CarLocation + type: model_class + CarRegistration: + description: CarRegistration(id, car, plate_number, text1, text2, text3, registration_date) + source: inventory.models.CarRegistration + type: model_class + Dealer: + description: Dealer(id, user, crn, vrn, arabic_name, name, phone_number, address, + logo, entity, joined_at, updated_at, slug) + source: inventory.models.Dealer + type: model_class + Staff: + description: Staff(id, staff_member, dealer, name, arabic_name, phone_number, + staff_type, active, created, updated, slug) + source: inventory.models.Staff + type: model_class + Customer: + description: Customer(id, dealer, customer_model, user, title, first_name, last_name, + gender, dob, email, national_id, phone_number, address, active, image, created, + updated, slug) + source: inventory.models.Customer + type: model_class + Organization: + description: Organization(id, dealer, customer_model, user, name, arabic_name, + crn, vrn, email, phone_number, address, logo, active, created, updated, slug) + source: inventory.models.Organization + type: model_class + Representative: + description: Representative(id, dealer, name, arabic_name, id_number, phone_number, + email, address) + source: inventory.models.Representative + type: model_class + Lead: + description: Lead(id, dealer, first_name, last_name, email, phone_number, address, + lead_type, customer, organization, id_car_make, id_car_model, source, channel, + staff, status, next_action, next_action_date, is_converted, converted_at, created, + updated, slug) + source: inventory.models.Lead + type: model_class + Schedule: + description: Schedule(id, name, func, hook, args, kwargs, schedule_type, minutes, + repeats, next_run, cron, task, cluster, intended_date_kwarg) + source: django_q.models.Schedule + type: model_class + LeadStatusHistory: + description: LeadStatusHistory(id, lead, old_status, new_status, changed_by, changed_at) + source: inventory.models.LeadStatusHistory + type: model_class + Opportunity: + description: Opportunity(id, dealer, customer, organization, car, crn, vrn, salary, + priority, stage, staff, lead, probability, amount, expected_revenue, vehicle_of_interest_make, + vehicle_of_interest_model, expected_close_date, created, updated, estimate, + slug, loss_reason) + source: inventory.models.Opportunity + type: model_class + Notes: + description: Notes(id, dealer, content_type, object_id, note, created_by, created, + updated) + source: inventory.models.Notes + type: model_class + Tasks: + description: Tasks(id, dealer, content_type, object_id, title, description, due_date, + completed, assigned_to, created_by, created, updated) + source: inventory.models.Tasks + type: model_class + Email: + description: Email(id, content_type, object_id, from_email, to_email, subject, + message, status, created_by, created, updated) + source: inventory.models.Email + type: model_class + Activity: + description: Activity(id, dealer, content_type, object_id, activity_type, notes, + created_by, created, updated) + source: inventory.models.Activity + type: model_class + Notification: + description: Notification(id, user, message, is_read, created) + source: inventory.models.Notification + type: model_class + Vendor: + description: Vendor(id, dealer, crn, vrn, vendor_model, arabic_name, name, contact_person, + phone_number, email, address, logo, active, created_at, slug) + source: inventory.models.Vendor + type: model_class + Payment: + description: Payment(id, amount, payment_method, reference_number, payment_date) + source: inventory.models.Payment + type: model_class + Refund: + description: Refund(id, payment, amount, reason, refund_date) + source: inventory.models.Refund + type: model_class + UserActivityLog: + description: UserActivityLog(id, user, action, timestamp) + source: inventory.models.UserActivityLog + type: model_class + SaleOrder: + description: SaleOrder(id, estimate, invoice, payment_method, comments, formatted_order_id, + opportunity, customer, car, agreed_price, down_payment_amount, trade_in_value, + trade_in_vehicle, loan_amount, total_paid_amount, remaining_balance, status, + order_date, expected_delivery_date, actual_delivery_date, cancelled_date, cancellation_reason, + created_at, updated_at, created_by, last_modified_by) + source: inventory.models.SaleOrder + type: model_class + CustomGroup: + description: CustomGroup(id, name, dealer, group) + source: inventory.models.CustomGroup + type: model_class + DealerSettings: + description: DealerSettings(id, dealer, invoice_cash_account, invoice_prepaid_account, + invoice_unearned_account, bill_cash_account, bill_prepaid_account, bill_unearned_account, + additional_info) + source: inventory.models.DealerSettings + type: model_class + PaymentHistory: + description: PaymentHistory(id, user, user_data, amount, currency, payment_date, + status, payment_method, transaction_id, invoice_number, order_reference, gateway_response, + gateway_name, description, is_recurring, billing_email, billing_address, created_at, + updated_at) + source: inventory.models.PaymentHistory + type: model_class + CarVIN: + description: CarVIN(id, vin, created) + source: api.models.CarVIN + type: model_class + EmailAddress: + description: EmailAddress(id, user, email, verified, primary) + source: account.models.EmailAddress + type: model_class + EmailConfirmation: + description: EmailConfirmation(id, email_address, created, sent, key) + source: account.models.EmailConfirmation + type: model_class + SocialApp: + description: SocialApp(id, provider, provider_id, name, client_id, secret, key, + settings) + source: socialaccount.models.SocialApp + type: model_class + SocialAccount: + description: SocialAccount(id, user, provider, uid, last_login, date_joined, extra_data) + source: socialaccount.models.SocialAccount + type: model_class + SocialToken: + description: SocialToken(id, app, account, token, token_secret, expires_at) + source: socialaccount.models.SocialToken + type: model_class + Request: + description: Request(id, path, query_params, raw_body, body, method, start_time, + view_name, end_time, time_taken, encoded_headers, meta_time, meta_num_queries, + meta_time_spent_queries, pyprofile, prof_file, num_sql_queries) + source: silk.models.Request + type: model_class + Response: + description: Response(id, request, status_code, raw_body, body, encoded_headers) + source: silk.models.Response + type: model_class + SQLQuery: + description: SQLQuery(id, query, start_time, end_time, time_taken, identifier, + request, traceback, analysis) + source: silk.models.SQLQuery + type: model_class + Profile: + description: Profile(id, name, start_time, end_time, request, time_taken, file_path, + line_num, end_line_num, func_name, exception_raised, dynamic) + source: silk.models.Profile + type: model_class + Token: + description: The default authorization token model. + source: authtoken.models.Token + type: model_class + TokenProxy: + description: Proxy mapping pk to user pk for use in admin. + source: authtoken.models.TokenProxy + type: model_class + BankAccountModel: + description: Base Bank Account Model Implementation + source: django_ledger.models.BankAccountModel + type: model_class + AccountModel: + description: Base Account Model from Account Model Abstract Class + source: django_ledger.models.AccountModel + type: model_class + ChartOfAccountModel: + description: Base ChartOfAccounts Model + source: django_ledger.models.ChartOfAccountModel + type: model_class + CustomerModel: + description: Base Customer Model Implementation + source: django_ledger.models.CustomerModel + type: model_class + UnitOfMeasureModel: + description: Base UnitOfMeasureModel from Abstract. + source: django_ledger.models.UnitOfMeasureModel + type: model_class + ItemTransactionModel: + description: Base ItemTransactionModel from Abstract. + source: django_ledger.models.ItemTransactionModel + type: model_class + ItemModel: + description: Base ItemModel from Abstract. + source: django_ledger.models.ItemModel + type: model_class + LedgerModel: + description: Base LedgerModel from Abstract. + source: django_ledger.models.LedgerModel + type: model_class + EntityUnitModel: + description: Base Model Class for EntityUnitModel + source: django_ledger.models.EntityUnitModel + type: model_class + VendorModel: + description: Base Vendor Model Implementation + source: django_ledger.models.VendorModel + type: model_class + EntityModel: + description: Entity Model Base Class From Abstract + source: django_ledger.models.EntityModel + type: model_class + EntityStateModel: + description: Entity State Model Base Class from Abstract. + source: django_ledger.models.EntityStateModel + type: model_class + EntityManagementModel: + description: EntityManagement Model Base Class From Abstract + source: django_ledger.models.EntityManagementModel + type: model_class + BillModel: + description: Base BillModel from Abstract. + source: django_ledger.models.BillModel + type: model_class + InvoiceModel: + description: Base Invoice Model from Abstract. + source: django_ledger.models.InvoiceModel + type: model_class + TransactionModel: + description: Base Transaction Model From Abstract. + source: django_ledger.models.TransactionModel + type: model_class + JournalEntryModel: + description: Journal Entry Model Base Class From Abstract + source: django_ledger.models.JournalEntryModel + type: model_class + PurchaseOrderModel: + description: Purchase Order Base Model + source: django_ledger.models.PurchaseOrderModel + type: model_class + EstimateModel: + description: Base EstimateModel Class. + source: django_ledger.models.EstimateModel + type: model_class + ClosingEntryModel: + description: ClosingEntryModel(created, updated, markdown_notes, uuid, entity_model, + ledger_model, closing_date, posted) + source: django_ledger.models.ClosingEntryModel + type: model_class + ClosingEntryTransactionModel: + description: Base ClosingEntryModel Class + source: django_ledger.models.ClosingEntryTransactionModel + type: model_class + ImportJobModel: + description: Transaction Import Job Model Base Class. + source: django_ledger.models.ImportJobModel + type: model_class + StagedTransactionModel: + description: Staged Transaction Model Base Class. + source: django_ledger.models.StagedTransactionModel + type: model_class + ChatLog: + description: 'Handles storage and representation of chat logs between users and + chatbot. + + + This class is designed to store chat logs, which include user messages, chatbot + + responses, their associated dealer, and timestamps of when the chat entries + are + + created. It supports linking chat logs to a specific dealer using a foreign + key + + relationship. The class provides an easy and structured way to manage and retrieve + + historical chat data. + + + :ivar dealer: The dealer associated with this chat log. + + :type dealer: Dealer + + :ivar user_message: The message sent by the user. + + :type user_message: str + + :ivar chatbot_response: The response generated by the chatbot. + + :type chatbot_response: str + + :ivar timestamp: The date and time when the chat log entry was created. + + :type timestamp: datetime' + source: haikalbot.models.ChatLog + type: model_class + AnalysisCache: + description: 'Model to cache analysis results for performance optimization. + + + This model stores cached results of model analysis operations to improve + + performance for repeated queries. It includes a hash of the prompt, user + + information, dealer ID, timestamps, and the cached result in JSON format. + + + :ivar prompt_hash: MD5 hash of the prompt + dealer_id + language + + :type prompt_hash: str + + :ivar user: The user who made the request (optional) + + :type user: User + + :ivar dealer_id: ID of the dealer associated with this cache entry + + :type dealer_id: int + + :ivar created_at: When the cache entry was created + + :type created_at: datetime + + :ivar updated_at: When the cache entry was last updated + + :type updated_at: datetime + + :ivar expires_at: When the cache entry expires + + :type expires_at: datetime + + :ivar result: The cached analysis result + + :type result: dict' + source: haikalbot.models.AnalysisCache + type: model_class + Service: + description: 'Represents a service provided by the appointment system. + + + Author: Adams Pierre David + + Version: 1.1.0 + + Since: 1.0.0' + source: appointment.models.Service + type: model_class + StaffMember: + description: StaffMember(id, user, slot_duration, lead_time, finish_time, appointment_buffer_time, + work_on_saturday, work_on_sunday, created_at, updated_at) + source: appointment.models.StaffMember + type: model_class + AppointmentRequest: + description: 'Represents an appointment request made by a client. + + + Author: Adams Pierre David + + Since: 1.0.0' + source: appointment.models.AppointmentRequest + type: model_class + AppointmentRescheduleHistory: + description: AppointmentRescheduleHistory(id, appointment_request, date, start_time, + end_time, staff_member, reason_for_rescheduling, reschedule_status, id_request, + created_at, updated_at) + source: appointment.models.AppointmentRescheduleHistory + type: model_class + Appointment: + description: 'Represents an appointment made by a client. It is created when the + client confirms the appointment request. + + + Author: Adams Pierre David + + Version: 1.1.0 + + Since: 1.0.0' + source: appointment.models.Appointment + type: model_class + Config: + description: 'Represents configuration settings for the appointment system. There + can only be one Config object in the database. + + If you want to change the settings, you must edit the existing Config object. + + + Author: Adams Pierre David + + Version: 1.1.0 + + Since: 1.1.0' + source: appointment.models.Config + type: model_class + PaymentInfo: + description: 'Represents payment information for an appointment. + + + Author: Adams Pierre David + + Version: 1.1.0 + + Since: 1.0.0' + source: appointment.models.PaymentInfo + type: model_class + EmailVerificationCode: + description: 'Represents an email verification code for a user when the email + already exists in the database. + + + Author: Adams Pierre David + + Version: 1.1.0 + + Since: 1.1.0' + source: appointment.models.EmailVerificationCode + type: model_class + PasswordResetToken: + description: 'Represents a password reset token for users. + + + Author: Adams Pierre David + + Version: 3.x.x + + Since: 3.x.x' + source: appointment.models.PasswordResetToken + type: model_class + DayOff: + description: DayOff(id, staff_member, start_date, end_date, description, created_at, + updated_at) + source: appointment.models.DayOff + type: model_class + WorkingHours: + description: WorkingHours(id, staff_member, day_of_week, start_time, end_time, + created_at, updated_at) + source: appointment.models.WorkingHours + type: model_class + Task: + description: Task(id, task_name, task_params, task_hash, verbose_name, priority, + run_at, repeat, repeat_until, queue, attempts, failed_at, last_error, locked_by, + locked_at, creator_content_type, creator_object_id) + source: background_task.models.Task + type: model_class + Success: + description: Success(id, name, func, hook, args, kwargs, result, group, cluster, + started, stopped, success, attempt_count) + source: django_q.models.Success + type: model_class + Failure: + description: Failure(id, name, func, hook, args, kwargs, result, group, cluster, + started, stopped, success, attempt_count) + source: django_q.models.Failure + type: model_class + OrmQ: + description: OrmQ(id, key, payload, lock) + source: django_q.models.OrmQ + type: model_class + Plan: + description: Plan(id, order, created, updated_at, name, description, default, + available, visible, customized, url) + source: plans.models.Plan + type: model_class + BillingInfo: + description: BillingInfo(id, created, updated_at, user, tax_number, name, street, + zipcode, city, country, shipping_name, shipping_street, shipping_zipcode, shipping_city) + source: plans.models.BillingInfo + type: model_class + UserPlan: + description: UserPlan(id, created, updated_at, user, plan, expire, active) + source: plans.models.UserPlan + type: model_class + Pricing: + description: Pricing(id, created, updated_at, name, period, url) + source: plans.models.Pricing + type: model_class + PlanPricing: + description: PlanPricing(id, created, updated_at, plan, pricing, price, order, + has_automatic_renewal, visible) + source: plans.models.PlanPricing + type: model_class + Quota: + description: Quota(id, order, created, updated_at, codename, name, unit, description, + is_boolean, url) + source: plans.models.Quota + type: model_class + PlanQuota: + description: PlanQuota(id, created, updated_at, plan, quota, value) + source: plans.models.PlanQuota + type: model_class + Order: + description: Order(id, created, updated_at, user, flat_name, plan, pricing, completed, + plan_extended_from, plan_extended_until, amount, tax, currency, status) + source: plans.models.Order + type: model_class + Invoice: + description: Invoice(id, created, updated_at, user, order, number, full_number, + type, issued, issued_duplicate, selling_date, payment_date, unit_price_net, + quantity, total_net, total, tax_total, tax, rebate, currency, item_description, + buyer_name, buyer_street, buyer_zipcode, buyer_city, buyer_country, buyer_tax_number, + shipping_name, shipping_street, shipping_zipcode, shipping_city, shipping_country, + require_shipment, issuer_name, issuer_street, issuer_zipcode, issuer_city, issuer_country, + issuer_tax_number) + source: plans.models.Invoice + type: model_class + RecurringUserPlan: + description: RecurringUserPlan(id, created, updated_at, user_plan, token, payment_provider, + pricing, amount, tax, currency, renewal_triggered_by, _has_automatic_renewal_backup_deprecated, + token_verified, card_expire_year, card_expire_month, card_masked_number) + source: plans.models.RecurringUserPlan + type: model_class + Sequence: + description: Sequence(name, last) + source: sequences.models.Sequence + type: model_class + ExportPDFSettings: + description: ExportPDFSettings(id, created, modified, title, active, page_size, + items_per_page, page_margin_mm, font_name, header_font_size, body_font_size, + logo, header_background_color, grid_line_color, grid_line_width, show_header, + show_logo, show_export_time, show_page_numbers, rtl_support, content_alignment, + header_alignment, title_alignment, table_spacing, max_chars_per_line) + source: django_pdf_actions.models.ExportPDFSettings + type: model_class + CompletedTask: + description: CompletedTask(id, task_name, task_params, task_hash, verbose_name, + priority, run_at, repeat, repeat_until, queue, attempts, failed_at, last_error, + locked_by, locked_at, creator_content_type, creator_object_id) + source: background_task.models.CompletedTask + type: model_class +user_workflows: + Add New Car: + description: How to add a new car to the inventory + steps: + - Navigate to the Inventory section by clicking 'Inventory' in the main menu + - Click the 'Add Car' button in the top right corner + - Enter the VIN number or scan it using the barcode scanner + - Select the car make from the dropdown menu + - Select the car series from the available options + - Select the trim level for the car + - Fill in additional details like color, mileage, and price + - Click 'Save' to add the car to inventory, or 'Save & Add Another' to continue + adding cars + source: manual_documentation + Create New Invoice: + description: How to create a new invoice + steps: + - Navigate to the Finance section by clicking 'Finance' in the main menu + - Click the 'Invoices' tab + - Click the 'Create New Invoice' button + - Select a customer from the dropdown or click 'Add New Customer' + - Select the car(s) to include in the invoice + - Add any additional services or parts by clicking 'Add Item' + - Set the payment terms and due date + - Click 'Save Draft' to save without finalizing, or 'Finalize Invoice' to complete + source: manual_documentation +templates: {} +glossary: {} diff --git a/haikalbot/ai_agent.py b/haikalbot/ai_agent.py index 331c8016..0ef515c4 100644 --- a/haikalbot/ai_agent.py +++ b/haikalbot/ai_agent.py @@ -20,7 +20,7 @@ from sqlalchemy.orm import relationship logger = logging.getLogger(__name__) # Configuration settings -LLM_MODEL = getattr(settings, 'MODEL_ANALYZER_LLM_MODEL', 'qwen:7b-chat') +LLM_MODEL = getattr(settings, 'MODEL_ANALYZER_LLM_MODEL', 'qwen3:8b') LLM_TEMPERATURE = getattr(settings, 'MODEL_ANALYZER_LLM_TEMPERATURE', 0.3) LLM_MAX_TOKENS = getattr(settings, 'MODEL_ANALYZER_LLM_MAX_TOKENS', 2048) CACHE_TIMEOUT = getattr(settings, 'MODEL_ANALYZER_CACHE_TIMEOUT', 3600) diff --git a/haikalbot/haikal_agent.py b/haikalbot/haikal_agent.py new file mode 100644 index 00000000..f09c9e52 --- /dev/null +++ b/haikalbot/haikal_agent.py @@ -0,0 +1,801 @@ +import asyncio +import sqlite3 +import json +import re +import logging +from typing import List, Dict, Any, Optional, Union +from dataclasses import dataclass, asdict +from enum import Enum +import os +from functools import reduce +import operator + +# Pydantic and AI imports +from pydantic import BaseModel, Field +from pydantic_ai import Agent, RunContext +from pydantic_ai.models.openai import OpenAIModel +from pydantic_ai.providers.openai import OpenAIProvider + +# Optional Django imports (if available) +try: + from django.apps import apps + from django.db import models, connection + from django.db.models import QuerySet, Q, F, Sum, Avg, Count, Max, Min + from django.core.exceptions import FieldDoesNotExist + from django.conf import settings + + DJANGO_AVAILABLE = True +except ImportError: + DJANGO_AVAILABLE = False + +# Optional database drivers +try: + import psycopg2 + + POSTGRESQL_AVAILABLE = True +except ImportError: + POSTGRESQL_AVAILABLE = False + +try: + import pymysql + + MYSQL_AVAILABLE = True +except ImportError: + MYSQL_AVAILABLE = False + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +# Configuration +class DatabaseConfig: + LLM_MODEL = settings.MODEL_ANALYZER_LLM_MODEL + LLM_BASE_URL = "http://localhost:11434/v1" + LLM_TEMPERATURE = 0.3 + MAX_RESULTS = 1000 + SUPPORTED_CHART_TYPES = ["bar", "line", "pie", "doughnut", "radar", "scatter"] + + +class DatabaseType(Enum): + SQLITE = "sqlite" + POSTGRESQL = "postgresql" + MYSQL = "mysql" + + + +@dataclass +class DatabaseConnection: + db_type: DatabaseType + connection_string: str + database_name: Optional[str] = None + host: Optional[str] = None + port: Optional[int] = None + user: Optional[str] = None + password: Optional[str] = None + schema_info: Optional[Dict] = None + + +@dataclass +class QueryResult: + status: str + data: Union[List[Dict], Dict] + metadata: Dict[str, Any] + chart_data: Optional[Dict] = None + language: str = "en" + error: Optional[str] = None + + def to_dict(self): + """Convert to dictionary for JSON serialization.""" + return asdict(self) + + +class DatabaseSchema(BaseModel): + tables: Dict[str, List[Dict[str, Any]]] = Field( + description="Database schema with table names as keys and column info as values" + ) + relationships: Optional[List[Dict[str, Any]]] = Field( + default=None, + description="Foreign key relationships between tables" + ) + + +class InsightRequest(BaseModel): + prompt: str = Field(description="Natural language query from user") + database_path: Optional[str] = Field(default=None, description="Path to database file (for SQLite)") + chart_type: Optional[str] = Field(default=None, description="Preferred chart type") + limit: Optional[int] = Field(default=1000, description="Maximum number of results") + language: Optional[str] = Field(default="auto", description="Response language preference") + use_django: Optional[bool] = Field(default=True, description="Use Django database if available") + + +class DatabaseInsightSystem: + def __init__(self, config: DatabaseConfig = None): + self.config = config or DatabaseConfig() + self.model = OpenAIModel( + model_name=self.config.LLM_MODEL, + provider=OpenAIProvider(base_url=self.config.LLM_BASE_URL) + ) + self.db_connection = None + self._setup_agents() + + def _setup_agents(self): + """Initialize the AI agents for schema analysis and query generation.""" + + # Query generation and execution agent + self.query_agent = Agent( + self.model, + deps_type=DatabaseSchema, + output_type=str, + system_prompt="""You are an intelligent database query generator and analyst. + Given a natural language prompt and database schema, you must: + + 1. ANALYZE the user's request in English or Arabic + 2. IDENTIFY relevant tables and columns from the schema + 3. GENERATE appropriate SQL query or analysis approach + 4. DETERMINE if aggregation, grouping, or joins are needed + 5. SUGGEST appropriate visualization type + 6. EXECUTE the query and provide insights + + Response format should be JSON: + { + "analysis": "Brief analysis of the request", + "query_type": "select|aggregate|join|complex", + "sql_query": "Generated SQL query", + "chart_suggestion": "bar|line|pie|etc", + "expected_fields": ["field1", "field2"], + "language": "en|ar" + } + + Handle both English and Arabic prompts. For Arabic text, respond in Arabic. + Focus on providing actionable insights, not just raw data.""" + ) + + def _get_django_database_config(self) -> Optional[DatabaseConnection]: + """Extract database configuration from Django settings.""" + if not DJANGO_AVAILABLE: + return None + + try: + # Get default database configuration + db_config = settings.DATABASES.get('default', {}) + if not db_config: + logger.warning("No default database configuration found in Django settings") + return None + + engine = db_config.get('ENGINE', '') + db_name = db_config.get('NAME', '') + host = db_config.get('HOST', 'localhost') + port = db_config.get('PORT', None) + user = db_config.get('USER', '') + password = db_config.get('PASSWORD', '') + + # Determine database type from engine + if 'sqlite' in engine.lower(): + db_type = DatabaseType.SQLITE + connection_string = db_name # For SQLite, NAME is the file path + elif 'postgresql' in engine.lower(): + db_type = DatabaseType.POSTGRESQL + port = port or 5432 + connection_string = f"postgresql://{user}:{password}@{host}:{port}/{db_name}" + elif 'mysql' in engine.lower(): + db_type = DatabaseType.MYSQL + port = port or 3306 + connection_string = f"mysql://{user}:{password}@{host}:{port}/{db_name}" + else: + logger.warning(f"Unsupported database engine: {engine}") + return None + + return DatabaseConnection( + db_type=db_type, + connection_string=connection_string, + database_name=db_name, + host=host, + port=port, + user=user, + password=password + ) + + except Exception as e: + logger.error(f"Failed to get Django database config: {e}") + return None + + def analyze_database_schema_sync(self, request: InsightRequest) -> DatabaseSchema: + """Synchronous wrapper for schema analysis.""" + return asyncio.run(self.analyze_database_schema(request)) + + async def analyze_database_schema(self, request: InsightRequest) -> DatabaseSchema: + """Extract and analyze database schema.""" + try: + # Try Django first if available and requested + if request.use_django and DJANGO_AVAILABLE: + django_config = self._get_django_database_config() + if django_config: + self.db_connection = django_config + return await self._analyze_django_schema() + + # Fallback to direct database connection + if request.database_path: + # Assume SQLite for direct file path + self.db_connection = DatabaseConnection( + db_type=DatabaseType.SQLITE, + connection_string=request.database_path + ) + return await self._analyze_sqlite_schema(request.database_path) + + raise ValueError("No database configuration available") + + except Exception as e: + logger.error(f"Schema analysis failed: {e}") + raise + + async def _analyze_sqlite_schema(self, db_path: str) -> DatabaseSchema: + """Analyze SQLite database schema.""" + try: + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + # Get table names + cursor.execute("SELECT name FROM sqlite_master WHERE type='table';") + tables = [row[0] for row in cursor.fetchall()] + + schema_data = {} + relationships = [] + + for table in tables: + # Get column information + cursor.execute(f"PRAGMA table_info({table})") + columns = [] + for col in cursor.fetchall(): + columns.append({ + "name": col[1], + "type": col[2], + "notnull": bool(col[3]), + "default_value": col[4], + "primary_key": bool(col[5]) + }) + schema_data[table] = columns + + # Get foreign key relationships + cursor.execute(f"PRAGMA foreign_key_list({table})") + for fk in cursor.fetchall(): + relationships.append({ + "from_table": table, + "from_column": fk[3], + "to_table": fk[2], + "to_column": fk[4] + }) + + conn.close() + return DatabaseSchema(tables=schema_data, relationships=relationships) + + except Exception as e: + logger.error(f"SQLite schema analysis failed: {e}") + raise + + async def _analyze_django_schema(self) -> DatabaseSchema: + """Analyze Django models schema.""" + if not DJANGO_AVAILABLE: + raise ImportError("Django is not available") + + schema_data = {} + relationships = [] + + for model in apps.get_models(): + table_name = model._meta.db_table + columns = [] + + for field in model._meta.get_fields(): + if not field.is_relation: + columns.append({ + "name": field.name, + "type": field.get_internal_type(), + "notnull": not getattr(field, 'null', True), + "primary_key": getattr(field, 'primary_key', False) + }) + else: + # Handle relationships + if hasattr(field, 'related_model') and field.related_model: + relationships.append({ + "from_table": table_name, + "from_column": field.name, + "to_table": field.related_model._meta.db_table, + "relationship_type": field.get_internal_type() + }) + + schema_data[table_name] = columns + + return DatabaseSchema(tables=schema_data, relationships=relationships) + + async def _analyze_postgresql_schema(self, connection_string: str) -> DatabaseSchema: + """Analyze PostgreSQL database schema.""" + if not POSTGRESQL_AVAILABLE: + raise ImportError("psycopg2 is not available") + + try: + import psycopg2 + from psycopg2.extras import RealDictCursor + + conn = psycopg2.connect(connection_string) + cursor = conn.cursor(cursor_factory=RealDictCursor) + + # Get table names + cursor.execute(""" + SELECT table_name + FROM information_schema.tables + WHERE table_schema = 'public' + """) + tables = [row['table_name'] for row in cursor.fetchall()] + + schema_data = {} + relationships = [] + + for table in tables: + # Get column information + cursor.execute(""" + SELECT column_name, data_type, is_nullable, column_default + FROM information_schema.columns + WHERE table_name = %s + ORDER BY ordinal_position + """, (table,)) + + columns = [] + for col in cursor.fetchall(): + columns.append({ + "name": col['column_name'], + "type": col['data_type'], + "notnull": col['is_nullable'] == 'NO', + "default_value": col['column_default'], + "primary_key": False # Will be updated below + }) + + # Get primary key information + cursor.execute(""" + SELECT column_name + FROM information_schema.key_column_usage + WHERE table_name = %s + AND constraint_name LIKE '%_pkey' + """, (table,)) + + pk_columns = [row['column_name'] for row in cursor.fetchall()] + for col in columns: + if col['name'] in pk_columns: + col['primary_key'] = True + + schema_data[table] = columns + + # Get foreign key relationships + cursor.execute(""" + SELECT kcu.column_name, + ccu.table_name AS foreign_table_name, + ccu.column_name AS foreign_column_name + FROM information_schema.table_constraints AS tc + JOIN information_schema.key_column_usage AS kcu + ON tc.constraint_name = kcu.constraint_name + JOIN information_schema.constraint_column_usage AS ccu + ON ccu.constraint_name = tc.constraint_name + WHERE tc.constraint_type = 'FOREIGN KEY' + AND tc.table_name = %s + """, (table,)) + + for fk in cursor.fetchall(): + relationships.append({ + "from_table": table, + "from_column": fk['column_name'], + "to_table": fk['foreign_table_name'], + "to_column": fk['foreign_column_name'] + }) + + conn.close() + return DatabaseSchema(tables=schema_data, relationships=relationships) + + except Exception as e: + logger.error(f"PostgreSQL schema analysis failed: {e}") + raise + + async def _analyze_mysql_schema(self, connection_string: str) -> DatabaseSchema: + """Analyze MySQL database schema.""" + if not MYSQL_AVAILABLE: + raise ImportError("pymysql is not available") + + try: + import pymysql + + # Parse connection string to get connection parameters + # Format: mysql://user:password@host:port/database + import urllib.parse + parsed = urllib.parse.urlparse(connection_string) + + conn = pymysql.connect( + host=parsed.hostname, + port=parsed.port or 3306, + user=parsed.username, + password=parsed.password, + database=parsed.path[1:], # Remove leading slash + cursorclass=pymysql.cursors.DictCursor + ) + + cursor = conn.cursor() + + # Get table names + cursor.execute("SHOW TABLES") + tables = [list(row.values())[0] for row in cursor.fetchall()] + + schema_data = {} + relationships = [] + + for table in tables: + # Get column information + cursor.execute(f"DESCRIBE {table}") + columns = [] + for col in cursor.fetchall(): + columns.append({ + "name": col['Field'], + "type": col['Type'], + "notnull": col['Null'] == 'NO', + "default_value": col['Default'], + "primary_key": col['Key'] == 'PRI' + }) + + schema_data[table] = columns + + # Get foreign key relationships + cursor.execute(f""" + SELECT + COLUMN_NAME, + REFERENCED_TABLE_NAME, + REFERENCED_COLUMN_NAME + FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE + WHERE TABLE_NAME = '{table}' + AND REFERENCED_TABLE_NAME IS NOT NULL + """) + + for fk in cursor.fetchall(): + relationships.append({ + "from_table": table, + "from_column": fk['COLUMN_NAME'], + "to_table": fk['REFERENCED_TABLE_NAME'], + "to_column": fk['REFERENCED_COLUMN_NAME'] + }) + + conn.close() + return DatabaseSchema(tables=schema_data, relationships=relationships) + + except Exception as e: + logger.error(f"MySQL schema analysis failed: {e}") + raise + + def _detect_language(self, text: str) -> str: + """Detect if text is Arabic or English.""" + arabic_chars = re.findall(r'[\u0600-\u06FF]', text) + return "ar" if len(arabic_chars) > len(text) * 0.3 else "en" + + def _execute_query_sync(self, query: str) -> List[Dict]: + """Synchronous wrapper for query execution.""" + return asyncio.run(self._execute_query(query)) + + async def _execute_query(self, query: str) -> List[Dict]: + """Execute query based on the current database connection.""" + if not self.db_connection: + raise ValueError("No database connection established") + + if self.db_connection.db_type == DatabaseType.SQLITE: + return await self._execute_sqlite_query(self.db_connection.connection_string, query) + # elif self.db_connection.db_type == DatabaseType.DJANGO and DJANGO_AVAILABLE: + # return await self._execute_django_query(query) + elif self.db_connection.db_type == DatabaseType.POSTGRESQL: + return await self._execute_postgresql_query(self.db_connection.connection_string, query) + elif self.db_connection.db_type == DatabaseType.MYSQL: + return await self._execute_mysql_query(self.db_connection.connection_string, query) + else: + raise ValueError(f"Unsupported database type: {self.db_connection.db_type}") + + async def _execute_sqlite_query(self, db_path: str, query: str) -> List[Dict]: + """Execute SQL query on SQLite database.""" + try: + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + cursor.execute(query) + + # Get column names + columns = [description[0] for description in cursor.description] + + # Fetch results and convert to dictionaries + results = cursor.fetchall() + data = [dict(zip(columns, row)) for row in results] + + conn.close() + return data + + except Exception as e: + logger.error(f"SQLite query execution failed: {e}") + raise + + async def _execute_django_query(self, query: str) -> List[Dict]: + """Execute raw SQL query using Django's database connection.""" + try: + from django.db import connection + + with connection.cursor() as cursor: + cursor.execute(query) + columns = [col[0] for col in cursor.description] + results = cursor.fetchall() + data = [dict(zip(columns, row)) for row in results] + + return data + + except Exception as e: + logger.error(f"Django query execution failed: {e}") + raise + + async def _execute_postgresql_query(self, connection_string: str, query: str) -> List[Dict]: + """Execute SQL query on PostgreSQL database.""" + try: + import psycopg2 + from psycopg2.extras import RealDictCursor + + conn = psycopg2.connect(connection_string) + cursor = conn.cursor(cursor_factory=RealDictCursor) + cursor.execute(query) + + results = cursor.fetchall() + data = [dict(row) for row in results] + + conn.close() + return data + + except Exception as e: + logger.error(f"PostgreSQL query execution failed: {e}") + raise + + async def _execute_mysql_query(self, connection_string: str, query: str) -> List[Dict]: + """Execute SQL query on MySQL database.""" + try: + import pymysql + import urllib.parse + + parsed = urllib.parse.urlparse(connection_string) + + conn = pymysql.connect( + host=parsed.hostname, + port=parsed.port or 3306, + user=parsed.username, + password=parsed.password, + database=parsed.path[1:], + cursorclass=pymysql.cursors.DictCursor + ) + + cursor = conn.cursor() + cursor.execute(query) + results = cursor.fetchall() + + conn.close() + return results + + except Exception as e: + logger.error(f"MySQL query execution failed: {e}") + raise + + def _prepare_chart_data(self, data: List[Dict], chart_type: str, fields: List[str]) -> Optional[Dict]: + """Prepare data for chart visualization.""" + if not data or not fields: + return None + + chart_type = chart_type.lower() + if chart_type not in self.config.SUPPORTED_CHART_TYPES: + chart_type = "bar" + + try: + # Extract labels and values + labels = [] + datasets = [] + + if len(fields) >= 1: + labels = [str(item.get(fields[0], "")) for item in data] + + if chart_type in ["pie", "doughnut"]: + # Single dataset for pie charts + values = [] + for item in data: + if len(fields) > 1: + try: + value = float(item.get(fields[1], 0) or 0) + except (ValueError, TypeError): + value = 1 + values.append(value) + else: + values.append(1) + + return { + "type": chart_type, + "labels": labels, + "data": values, + "backgroundColor": [ + f"rgba({50 + i * 30}, {100 + i * 25}, {200 + i * 20}, 0.7)" + for i in range(len(values)) + ] + } + else: + # Multiple datasets for other chart types + for i, field in enumerate(fields[1:], 1): + try: + dataset_values = [] + for item in data: + try: + value = float(item.get(field, 0) or 0) + except (ValueError, TypeError): + value = 0 + dataset_values.append(value) + + datasets.append({ + "label": field, + "data": dataset_values, + "backgroundColor": f"rgba({50 + i * 40}, {100 + i * 30}, 235, 0.6)", + "borderColor": f"rgba({50 + i * 40}, {100 + i * 30}, 235, 1.0)", + "borderWidth": 2 + }) + except Exception as e: + logger.warning(f"Error processing field {field}: {e}") + + return { + "type": chart_type, + "labels": labels, + "datasets": datasets + } + + except Exception as e: + logger.error(f"Chart preparation failed: {e}") + return None + + def get_insights_sync(self, request: InsightRequest) -> Dict[str, Any]: + """Synchronous wrapper for get_insights - for Django views.""" + try: + result = asyncio.run(self.get_insights(request)) + return result.to_dict() + except Exception as e: + logger.error(f"Synchronous insight generation failed: {e}") + return { + "status": "error", + "data": [], + "metadata": {}, + "error": str(e), + "language": "en" + } + + async def get_insights(self, request: InsightRequest) -> QueryResult: + """Main method to get database insights from natural language prompt.""" + try: + # Detect language + language = self._detect_language(request.prompt) if request.language == "auto" else request.language + + # Analyze database schema + schema = await self.analyze_database_schema(request) + + # Generate query plan using AI + query_response = await self.query_agent.run( + f"User prompt: {request.prompt}\nLanguage: {language}", + database_schema=schema + ) + + # Parse AI response + try: + query_plan = json.loads(query_response.output) + except json.JSONDecodeError: + # Fallback: extract SQL from response + sql_match = re.search(r'SELECT.*?;', query_response.output, re.IGNORECASE | re.DOTALL) + if sql_match: + query_plan = { + "sql_query": sql_match.group(0), + "chart_suggestion": "bar", + "expected_fields": [], + "language": language + } + else: + raise ValueError("Could not parse AI response") + + # Execute query + sql_query = query_plan.get("sql_query", "") + if not sql_query: + raise ValueError("No SQL query generated") + + data = await self._execute_query(sql_query) + + # Prepare chart data + chart_data = None + chart_type = request.chart_type or query_plan.get("chart_suggestion", "bar") + expected_fields = query_plan.get("expected_fields", []) + + if data and expected_fields: + chart_data = self._prepare_chart_data(data, chart_type, expected_fields) + elif data: + # Use first few fields if no specific fields suggested + available_fields = list(data[0].keys()) if data else [] + chart_data = self._prepare_chart_data(data, chart_type, available_fields[:3]) + + # Prepare result + return QueryResult( + status="success", + data=data[:request.limit] if data else [], + metadata={ + "total_count": len(data) if data else 0, + "query": sql_query, + "analysis": query_plan.get("analysis", ""), + "fields": expected_fields or (list(data[0].keys()) if data else []), + "database_type": self.db_connection.db_type.value if self.db_connection else "unknown" + }, + chart_data=chart_data, + language=language + ) + + except Exception as e: + logger.error(f"Insight generation failed: {e}") + return QueryResult( + status="error", + data=[], + metadata={}, + error=str(e), + language=language if 'language' in locals() else "en" + ) + + # # Static method for Django view compatibility + # @staticmethod + # def get_insights(django_request, prompt: str, **kwargs) -> Dict[str, Any]: + # """ + # Static method compatible with your Django view. + # This method signature matches what your view is calling. + # + # Args: + # django_request: Django HttpRequest object (not used but kept for compatibility) + # prompt: Natural language query string + # **kwargs: Additional parameters + # + # Returns: + # Dictionary with query results + # """ + # try: + # # Create system instance + # system = DatabaseInsightSystem() + # + # # Extract language from Django request if available + # language = "auto" + # if hasattr(django_request, 'LANGUAGE_CODE'): + # language = django_request.LANGUAGE_CODE + # + # # Create insight request + # insight_request = InsightRequest( + # prompt=prompt, + # language=language, + # use_django=True, + # **kwargs + # ) + # + # # Get insights synchronously + # return system.get_insights_sync(insight_request) + # + # except Exception as e: + # logger.error(f"Static get_insights failed: {e}") + # return { + # "status": "error", + # "data": [], + # "metadata": {}, + # "error": str(e), + # "language": language if 'language' in locals() else "en" + # } + + +# Convenience function for Django views (alternative approach) +def analyze_prompt_sync(prompt: str, **kwargs) -> Dict[str, Any]: + """ + Synchronous function to analyze a prompt and return insights. + Perfect for Django views. + + Args: + prompt: Natural language query + **kwargs: Additional parameters for InsightRequest + + Returns: + Dictionary with query results + """ + system = DatabaseInsightSystem() + request = InsightRequest(prompt=prompt, **kwargs) + return system.get_insights_sync(request) \ No newline at end of file diff --git a/haikalbot/haikal_kb.yaml b/haikalbot/haikal_kb.yaml index 00b23b7a..e5eef292 100644 --- a/haikalbot/haikal_kb.yaml +++ b/haikalbot/haikal_kb.yaml @@ -1,38 +1,2923 @@ metadata: system_name: Haikal - version: 1.0 - language: bilingual - roles: - - admin - - dealer - - branch - - supplier - + version: '1.0' + generated_from: Django features: - add_car: - description: Add a new car to inventory - steps: - - Navigate to the "Inventory" section - - Click "Add New Car" - - Enter required fields: VIN, Make, Model, Year - - Optional: Upload custom card, add warranty or insurance - - Click "Save" - permissions: ["admin", "dealer"] - related_terms: ["chassis", "هيكل", "السيارة"] + auth_login: + description: 'Persist a user id and a backend in the request. This way a user + doesn''t - create_invoice: - description: Create a sale or purchase invoice - steps: - - Go to the "Invoices" page - - Click "New Invoice" - - Choose Type: Sale or Purchase - - Select invoice_from and invoice_to - - Link existing order(s) - - Confirm and save - permissions: ["admin", "dealer"] - notes: Use sale for customer transactions, purchase for supplier buys + have to reauthenticate on every request. Note that data set during -glossary: - VIN: Vehicle Identification Number or chassis number (هيكل السيارة) - custom_card: Official car registration document (استمارة) - adjustment: Any cost added to or subtracted from an order \ No newline at end of file + the anonymous session is retained when the user logs in.' + source: django.contrib.auth.views.auth_login + type: view_function + auth_logout: + description: 'Remove the authenticated user''s ID from the request and flush their + session + + data.' + source: django.contrib.auth.views.auth_logout + type: view_function + csrf_protect: + description: 'This decorator adds CSRF protection in exactly the same way as + + CsrfViewMiddleware, but it can be used on a per view basis. Using both, or + + using the decorator multiple times, is harmless and efficient.' + source: django.contrib.auth.views.csrf_protect + type: view_function + get_current_site: + description: 'Check if contrib.sites is installed and return either the current + + ``Site`` object or a ``RequestSite`` object based on the request.' + source: allauth.socialaccount.views.get_current_site + type: view_function + get_user_model: + description: Return the User model that is active in this project. + source: appointment.views.get_user_model + type: view_function + login_not_required: + description: Decorator for views that allows access to unauthenticated requests. + source: debug_toolbar.views.login_not_required + type: view_function + login_required: + description: 'Decorator for views that checks that the user is logged in, redirecting + + to the log-in page if necessary.' + source: allauth.socialaccount.views.login_required + type: view_function + logout_then_login: + description: Log out the user if they are logged in. Then redirect to the login + page. + source: django.contrib.auth.views.logout_then_login + type: view_function + method_decorator: + description: Convert a function decorator into a method decorator + source: allauth.socialaccount.providers.google.views.method_decorator + type: view_function + never_cache: + description: Decorator that adds headers to a response so that it will never be + cached. + source: allauth.account.views.never_cache + type: view_function + redirect_to_login: + description: Redirect the user to the login page, passing the given 'next' page. + source: django.contrib.auth.views.redirect_to_login + type: view_function + resolve_url: + description: "Return a URL appropriate for the arguments passed.\n\nThe arguments\ + \ could be:\n\n * A model: the model's `get_absolute_url()` function will\ + \ be called.\n\n * A view name, possibly with arguments: `urls.reverse()`\ + \ will be used\n to reverse-resolve the name.\n\n * A URL, which will\ + \ be returned as-is." + source: django.contrib.auth.views.resolve_url + type: view_function + sensitive_post_parameters: + description: "Indicate which POST parameters used in the decorated view are sensitive,\n\ + so that those parameters can later be treated in a special way, for example\n\ + by hiding them when logging unhandled exceptions.\n\nAccept two forms:\n\n*\ + \ with specified parameters:\n\n @sensitive_post_parameters('password', 'credit_card')\n\ + \ def my_view(request):\n pw = request.POST['password']\n cc\ + \ = request.POST['credit_card']\n ...\n\n* without any specified parameters,\ + \ in which case consider all\n variables are sensitive:\n\n @sensitive_post_parameters()\n\ + \ def my_view(request)\n ..." + source: allauth.account.views.sensitive_post_parameters + type: view_function + update_session_auth_hash: + description: 'Updating a user''s password logs out all sessions for the user. + + + Take the current request and the updated user object from which the new + + session hash will be derived and update the session hash appropriately to + + prevent a password change from logging out the session from which the + + password was changed.' + source: django.contrib.auth.views.update_session_auth_hash + type: view_function + url_has_allowed_host_and_scheme: + description: 'Return ``True`` if the url uses an allowed host and a safe scheme. + + + Always return ``False`` on an empty url. + + + If ``require_https`` is ``True``, only ''https'' will be considered a valid + + scheme, as opposed to ''http'' and ''https'' with the default, ``False``. + + + Note: "True" doesn''t entail that a URL is "safe". It may still be e.g. + + quoted incorrectly. Ensure to also use django.utils.encoding.iri_to_uri() + + on the path component of untrusted URLs.' + source: django.contrib.auth.views.url_has_allowed_host_and_scheme + type: view_function + urlsafe_base64_decode: + description: 'Decode a base64 encoded string. Add back any trailing equal signs + that + + might have been stripped.' + source: appointment.views.urlsafe_base64_decode + type: view_function + urlunsplit: + description: 'Combine the elements of a tuple as returned by urlsplit() into a + + complete URL as a string. The data argument can be any five-item iterable. + + This may result in a slightly different, but equivalent URL, if the URL that + + was parsed originally had unnecessary delimiters (for example, a ? with an + + empty query; the RFC states that these are equivalent).' + source: django.contrib.auth.views.urlunsplit + type: view_function + shortcut: + description: Redirect to an object's page based on a content-type ID and an object + ID. + source: django.contrib.contenttypes.views.shortcut + type: view_function + serve: + description: "Serve static files below a given point in the directory structure\ + \ or\nfrom locations inferred from the staticfiles finders.\n\nTo use, put a\ + \ URL pattern such as::\n\n from django.contrib.staticfiles import views\n\ + \n path('', views.serve)\n\nin your URLconf.\n\nIt uses the django.views.static.serve()\ + \ view to serve the found files." + source: django.contrib.staticfiles.views.serve + type: view_function + BillDeleteView: + description: 'Deletes a specific BillModel instance based on the primary key (pk). + + This view requires the user to be logged in and have the appropriate + + permission to delete a bill. After the delete operation is successful, + + the user is redirected to the bill list view. + + + :param request: The HTTP request object. + + :type request: HttpRequest + + :param pk: Primary key of the BillModel instance to be deleted. + + :type pk: int + + :return: Redirect to the bill list view after successful deletion. + + :rtype: HttpResponseRedirect' + source: inventory.views.BillDeleteView + type: view_function + CarTransferPreviewView: + description: "Handles the preview of car transfer details and ensures that a user\ + \ has appropriate\npermissions to view the transfer based on their associated\ + \ dealer.\n\nThis view checks if the car transfer's destination dealer matches\ + \ the current user's\nassociated dealer type. If not, the user is redirected\ + \ to the car detail page. Otherwise,\nit renders the transfer preview page with\ + \ the relevant transfer details.\n\n:param request: The HTTP request object\n\ + :type request: django.http.HttpRequest\n:param car_pk: The primary key of the\ + \ car related to the transfer\n:type car_pk: int\n:param transfer_pk: The primary\ + \ key of the car transfer to preview\n:type transfer_pk: int\n:return: An HTTP\ + \ response rendering the transfer preview page or redirecting\n to the\ + \ car detail page\n:rtype: django.http.HttpResponse" + source: inventory.views.CarTransferPreviewView + type: view_function + DealerSettingsView: + description: "Handles dealer settings view where dealers can update their financial\ + \ and\npayment account settings. This view ensures validation and reassigns\ + \ form\nfields dynamically based on the dealer's account roles.\n\n:param request:\ + \ The HTTP request object received from the client. This parameter\n contains\ + \ data including HTTP headers, session, and POST data if applicable.\n:type\ + \ request: HttpRequest\n:param pk: Primary key representing the dealer for whom\ + \ the settings are being\n retrieved or modified. This identifier is used\ + \ to fetch or update dealer-\n specific settings from the database.\n:type\ + \ pk: int\n:return: An HTTP response rendering the dealer settings form or redirecting\n\ + \ to the dealer detail view after successful form submission.\n:rtype: HttpResponse" + source: inventory.views.DealerSettingsView + type: view_function + GroupDeleteview: + description: "Handles the deletion of a specific group instance. This view ensures\ + \ that only\nauthenticated users can perform the deletion. Upon successful deletion,\ + \ a\nsuccess message is displayed, and the user is redirected to the group list\ + \ page.\n\n:param request: The HTTP request object that contains metadata about\ + \ the\n request context and user information. Must be an authenticated user.\n\ + :param pk: The primary key of the group instance to be deleted.\n It specifies\ + \ which group to retrieve and delete.\n:return: The HTTP response that redirects\ + \ the user to the group list page\n after the group is successfully deleted." + source: inventory.views.GroupDeleteview + type: view_function + GroupPermissionView: + description: "Handles the view for adding or modifying permissions of a specific\ + \ group. This view\nfetches the group based on the primary key passed as a parameter,\ + \ and either displays\na form for editing permissions or processes the submitted\ + \ permissions.\n\nIf the request method is POST, the permissions of the group\ + \ are cleared and updated\nbased on the submitted data. A success message is\ + \ displayed upon completion, and\nthe user is redirected to the group's detail\ + \ page.\n\nIn case of a GET request, the view renders the form pre-filled with\ + \ the group's\ncurrent permissions.\n\n:param request: The HTTP request object.\n\ + :type request: HttpRequest\n:param pk: The primary key of the group whose permissions\ + \ are being modified.\n:type pk: int\n:return: The HTTP response depending on\ + \ the request type. For GET requests, renders\n the permission form for the\ + \ specified group. For POST requests, clears and updates\n the group's permissions\ + \ and redirects to the group's detail page.\n:rtype: HttpResponse" + source: inventory.views.GroupPermissionView + type: view_function + JournalEntryDeleteView: + description: "Handles the deletion of a specific journal entry. This view facilitates\n\ + the deletion of a journal entry identified by its primary key (pk). If the\n\ + deletion is successful, the user is redirected to the list of journal entries\n\ + for the associated ledger. If the deletion cannot proceed, an error message\n\ + is displayed, and the user is redirected back to the journal entry list.\n\n\ + :param request: The HTTP request object.\n:type request: HttpRequest\n:param\ + \ pk: The primary key (pk) of the journal entry to be deleted.\n:type pk: int\n\ + :return: A rendered HTML response for GET requests or a redirect upon\n \ + \ successful/failed deletion.\n:rtype: HttpResponse" + source: inventory.views.JournalEntryDeleteView + type: view_function + JournalEntryTransactionsView: + description: "Handles the retrieval and display of journal entry transactions\ + \ for a specific journal\nentry instance. It retrieves the journal entry and\ + \ its associated transactions, ordering\nthe transactions by account code. The\ + \ data is then rendered using the specified template.\n\n:param request: The\ + \ HTTP request object.\n:type request: django.http.HttpRequest\n:param pk: The\ + \ primary key of the journal entry to be retrieved.\n:type pk: int\n:return:\ + \ An HTTP response with the rendered template, including the journal entry and\n\ + \ its transactions.\n:rtype: django.http.HttpResponse" + source: inventory.views.JournalEntryTransactionsView + type: view_function + LeadDeleteView: + description: 'Handles the deletion of a Lead along with its associated customer + and potentially + + a related user account in the system. Ensures proper permissions and login + + are required before the operation is performed. Provides a success message + + after the deletion is complete. + + + :param request: The HTTP request object specific to the current user. + + :param pk: The primary key identifier of the Lead to be deleted. + + :return: An HTTP redirect response to the lead list page.' + source: inventory.views.LeadDeleteView + type: view_function + OrganizationDeleteView: + description: 'Handles the deletion of an organization based on the provided primary + key (pk). Looks up + + the organization and its corresponding user by email, attempts to delete both, + and provides + + appropriate success or error feedback to the user. In case of failure, an error + message is shown, + + while successful deletion redirects to the organization list. + + + :param request: The HTTP request object containing metadata about the request. + + :type request: HttpRequest + + :param pk: The primary key of the organization to be deleted. + + :type pk: int + + :return: An HTTP response redirecting to the organization list view. + + :rtype: HttpResponseRedirect' + source: inventory.views.OrganizationDeleteView + type: view_function + PaymentCreateView: + description: "Handles the creation of a payment entry associated with an invoice\ + \ or bill. Validates\nthe payment data against the model's current state and\ + \ reflects the changes in\ninvoice or bill records. Provides appropriate error\ + \ messages for invalid conditions\nsuch as exceeding payable amounts or attempting\ + \ payment for already fully paid models.\n\nIf successfully processed, the payment\ + \ details are saved, and the model is updated\naccordingly. This view regulates\ + \ payment for dealer-associated entities while\nensuring the model consistency.\n\ + \nThe view renders a form to submit payment details, and pre-populates the form\ + \ fields\nwith default data for the associated model if necessary.\n\n:param\ + \ request: The HTTP request object containing user request data and session\n\ + \ information. This is required to handle the request and apply the appropriate\n\ + \ processing rules.\n:param pk: The primary key of the invoice or bill being\ + \ processed. It is used to\n load the appropriate model instance for payment\ + \ processing.\n:return: An HTTP response object. Depending on the circumstances,\ + \ the response may\n redirect to the detail view of the processed invoice\ + \ or bill, re-render the\n payment form with error messages or indicate success\ + \ in payment creation." + source: inventory.views.PaymentCreateView + type: view_function + PaymentDetailView: + description: "This function handles the detail view for a payment by fetching\ + \ a journal entry\nand its associated transactions. It ensures that the request\ + \ is authenticated\nand the user has permission to view the journal entry model.\n\ + \n:param request: The HTTP request object.\n:type request: HttpRequest\n:param\ + \ pk: The primary key of the journal entry for which details are to be fetched.\n\ + :type pk: int\n:return: An HTTP response rendering the payment details template\ + \ with the journal\n entry and its associated transactions.\n:rtype:\ + \ HttpResponse" + source: inventory.views.PaymentDetailView + type: view_function + PaymentListView: + description: 'Handles the view for listing payment information associated with + the journals of a specific + + entity. This view is protected to ensure only authenticated and authorized users + can + + access it. + + + The function retrieves the related dealer object based on the current user session, + extracts + + the associated entity, and fetches all journal entries linked to the entity. + This data is + + then passed into the template for rendering. + + + :param request: The HTTP request object containing user context. + + :type request: HttpRequest + + + :return: The rendered HTML response displaying the list of payments. + + :rtype: HttpResponse' + source: inventory.views.PaymentListView + type: view_function + UserDeleteview: + description: 'Deletes a user and its associated staff member from the database + and redirects + + to the user list page. Displays a success message upon successful deletion + + of the user. + + + :param request: The HTTP request object representing the incoming request. + + :param pk: The primary key (ID) of the staff member to be deleted. + + :return: An HTTP redirect to the user list page.' + source: inventory.views.UserDeleteview + type: view_function + UserGroupView: + description: "Handles the assignment of user groups to a specific staff member.\ + \ This view\nallows updating the groups a staff member belongs to via a form\ + \ submission.\nIt processes both GET and POST requests, ensuring appropriate\ + \ group\nassignments are managed and feedback is provided to the user via messages.\n\ + \n:param request: HttpRequest object representing the HTTP request.\n:type request:\ + \ HttpRequest\n:param pk: Primary key of the staff member whose groups are being\ + \ updated.\n:type pk: int\n\n:return: Renders the user group form for GET requests\ + \ or redirects to the\n user detail page after successful submission for\ + \ POST requests.\n:rtype: HttpResponse or HttpResponseRedirect" + source: inventory.views.UserGroupView + type: view_function + account_delete: + description: 'Handles the deletion of an account object identified by its primary + key (pk). Ensures + + that the user has the necessary permissions to perform the deletion. Successfully + + deletes the account and redirects to the account list view with a success message. + + + :param request: The HTTP request object representing the current user and request + data. + + :type request: HttpRequest + + :param pk: The primary key of the account to be deleted. + + :type pk: int + + :return: An HTTP redirect response to the account list page. + + :rtype: HttpResponse' + source: inventory.views.account_delete + type: view_function + accruable_net_summary: + description: 'A convenience function that computes current net summary of accruable + models. + + "net_30" group indicates the total amount is due in 30 days or less. + + "net_0" group indicates total past due amount. + + + :param queryset: Accruable Objects Queryset. + + :return: A dictionary summarizing current net summary 0,30,60,90,90+ bill open + amounts.' + source: django_ledger.views.accruable_net_summary + type: view_function + add_activity_to_customer: + description: "Adds an activity to a specific customer.\n\nThis function allows\ + \ adding a new activity to a customer identified by their\nprimary key (`pk`).\ + \ It retrieves the customer object, processes the form for\nactivity creation,\ + \ and saves it. If the request method is POST, it validates\nthe form and associates\ + \ the activity with the respective customer. Upon\nsuccessful save, it redirects\ + \ to the customer detail view. If the request\nmethod is GET, it renders a form\ + \ for activity submission.\n\n:param request: The HTTP request object containing\ + \ metadata about the request.\n:type request: HttpRequest\n:param pk: The primary\ + \ key of the customer to which the activity will be added.\n:type pk: int\n\ + :return: An HTTP response rendered with the activity form in the context of\n\ + \ the customer, or a redirect response to the customer detail view upon\n\ + \ successful activity creation.\n:rtype: HttpResponse" + source: inventory.views.add_activity_to_customer + type: view_function + add_activity_to_lead: + description: 'Handles the process of adding a new activity to a specific lead. + This includes + + rendering a form for user input, validating the form submission, and saving + the + + new activity if the input is valid. If the method is GET, it will simply + + render the form. If the method is POST, it checks the form validity, creates + + the activity, associates it with the lead, and saves it to the database. + + + :param request: The HTTP request object containing metadata about the request + made + + :param pk: The primary key of the lead to which the activity is to be added + + :return: An HTTP response that either renders the form or redirects to the lead + detail page' + source: inventory.views.add_activity_to_lead + type: view_function + add_note_to_customer: + description: "This function allows authenticated users to add a note to a specific\ + \ customer. The\nnote creation is handled by a form, which is validated after\ + \ submission. If the form\nis valid, the note is saved and associated with the\ + \ specified customer. On successful\nsubmission, the user is redirected to the\ + \ customer detail page. If the request method\nis not POST, an empty form is\ + \ rendered.\n\n:param request: The HTTP request object containing metadata and\ + \ the method type\n (e.g., GET, POST). Should be an HttpRequest\ + \ instance.\n:param customer_id: The unique identifier (UUID) of the customer\ + \ to whom the note\n is to be added.\n:return: An HTTP response.\ + \ In the case of a successful POST request, the function\n returns a\ + \ redirect response to the customer detail page. For a GET or invalid\n \ + \ POST request, it renders the note form template with context including\n\ + \ the form and customer." + source: inventory.views.add_note_to_customer + type: view_function + add_note_to_lead: + description: "Adds a note to a specific lead. This view is accessible only to\ + \ authenticated\nusers. The function handles the POST request to create a new\ + \ note associated\nwith a lead. Upon successful submission, the note is linked\ + \ to the provided lead,\nand the user is redirected to the lead's detail page.\ + \ If the request method is\nnot POST, it initializes an empty form to add a\ + \ note.\n\n:param request: The HTTP request object\n:type request: HttpRequest\n\ + :param pk: The primary key of the lead to which the note will be added\n:type\ + \ pk: int\n:return: HTTP response object. Redirects to the lead detail page\ + \ on successful\n note creation or renders the note form template for GET\ + \ or invalid POST requests.\n:rtype: HttpResponse" + source: inventory.views.add_note_to_lead + type: view_function + add_note_to_opportunity: + description: 'Add a note to a specific opportunity identified by its primary key. + + + This function handles the addition of a note to an existing opportunity + + by processing a POST request that includes the note content. If the note + + content is missing, an error message will be displayed. If the operation + + is successful, the note is saved, and a success message is returned. + + + :param request: The HTTP request object representing the client''s request. + + :param pk: The primary key of the Opportunity to which the note is to be added. + + :type pk: int + + :return: A redirect response to the detailed view of the opportunity.' + source: inventory.views.add_note_to_opportunity + type: view_function + apply_search_filters: + description: "Apply search filters to a queryset based on a query string.\n\n\ + This function filters the provided queryset by applying a Q object for all\n\ + CharField, TextField, or EmailField fields in the model. It checks if the\n\ + query exists within these fields using case-insensitive containment (icontains).\n\ + If the query string is empty or None, the original queryset is returned\nwithout\ + \ any modifications.\n\n:param queryset: The initial queryset to apply the search\ + \ filters on.\n:param query: The search string to match against the model fields.\ + \ If None\n or an empty string, no filtering is applied.\n:return: A filtered\ + \ queryset that satisfies the search condition." + source: inventory.views.apply_search_filters + type: view_function + assign_car_makes: + description: "Assigns car makes to a dealer.\n\nThis function handles both the\ + \ display and processing of a form that allows\na dealer to assign or modify\ + \ their associated car makes. If the request\nmethod is POST, it validates and\ + \ saves the submitted form data. If the\nmethod is not POST, it displays a form\ + \ prefilled with the existing car makes\nassociated with the dealer.\n\n:param\ + \ request: The HTTP request object containing information about the\n current\ + \ request.\n:type request: HttpRequest\n:return: A rendered HTML response for\ + \ GET requests, or a redirect to the\n dealer detail page after successful\ + \ form submission.\n:rtype: HttpResponse" + source: inventory.views.assign_car_makes + type: view_function + bank_account_delete: + description: "Delete a bank account entry from the database.\n\nThis view handles\ + \ the deletion of a bank account record specified by its\nprimary key (pk).\ + \ It renders a deletion confirmation page and processes the\ndeletion if the\ + \ request method is POST. Upon successful deletion, the user is\nredirected\ + \ to the list of bank accounts and a success message is displayed.\n\n:param\ + \ request: The HTTP request object representing the client's request.\n It\ + \ contains data such as request type (GET or POST) and session\n information.\n\ + :type request: HttpRequest\n:param pk: The primary key of the bank account model\ + \ instance to be deleted.\n:type pk: int\n:return: Returns an HttpResponse object.\ + \ This can be an HTTP redirect to the\n bank account list page upon successful\ + \ deletion, or an HTML response\n rendering the confirmation template if\ + \ accessed via GET.\n:rtype: HttpResponse" + source: inventory.views.bank_account_delete + type: view_function + bill_create: + description: "Handles creation of a bill in the system, including the validation\ + \ of input data,\ncreation of transactions associated with the bill, and rendering\ + \ of the appropriate\nresponse or form. Ensures the user creating the bill has\ + \ the necessary permissions and\ncorrect input parameters for successful bill\ + \ creation and itemization.\n\n:param request: Django HttpRequest object containing\ + \ metadata and data of the HTTP request.\n:type request: HttpRequest\n:return:\ + \ JsonResponse with success/error information if the request is processed,\n\ + \ or HttpResponse rendering the form for bill creation.\n:rtype: JsonResponse\ + \ or HttpResponse" + source: inventory.views.bill_create + type: view_function + bill_mark_as_approved: + description: "Marks a bill as approved for the given bill ID (primary key) if\ + \ it is not\nalready approved. This action can only be completed by an authorized\ + \ user\nwith the corresponding permissions. Once the bill is approved, it is\ + \ saved\nand a success message is displayed. If the bill is already approved,\ + \ an\nerror message is shown instead.\n\n:param request: HttpRequest object\ + \ representing the current request.\n:param pk: Primary key of the bill to be\ + \ marked as approved.\n:return: HttpResponseRedirect to the bill detail page\ + \ after the operation is\n completed or an error/success message is\ + \ set." + source: inventory.views.bill_mark_as_approved + type: view_function + bill_mark_as_paid: + description: 'Marks a bill as paid if certain conditions are met and updates the + ledger accordingly. + + + This function is used to mark a specific bill as paid. It verifies whether the + bill + + is already paid or if the amount paid matches the amount due. If the conditions + are + + met, it updates the bill''s status, locks the journal entries in the associated + ledger, + + posts them, and saves the changes. Appropriate success or error messages are + displayed + + to the user, and the user is redirected to the bill details page. + + + :param request: The HTTP request object containing details about the request + made by the user. + + :type request: HttpRequest + + :param pk: The primary key of the bill to be marked as paid. + + :type pk: int + + :return: A redirect response to the bill details page. + + :rtype: HttpResponseRedirect' + source: inventory.views.bill_mark_as_paid + type: view_function + car_history: + description: "Fetch and display the history of activities related to a specific\ + \ car.\n\nThis view retrieves a car object based on its primary key (pk) and\ + \ gathers\nall related activity records where the content type corresponds to\ + \ the car\nmodel. The retrieved data is then rendered into a specified HTML\ + \ template\nfor presentation.\n\n:param request: The HTTP request object that\ + \ contains metadata about the\n request made by the client.\n:type request:\ + \ HttpRequest\n:param pk: The primary key of the car object to retrieve.\n:type\ + \ pk: int\n:return: An HTTP response with the rendered car history HTML template,\n\ + \ including the car and its associated activities as context data.\n:rtype:\ + \ HttpResponse" + source: inventory.views.car_history + type: view_function + car_transfer_accept_reject: + description: "Handles the acceptance or rejection of a car transfer request. Based\ + \ on the\n`status` parameter obtained from the query string, the function updates\ + \ the\ntransfer status to either 'accept' or 'reject'. If the transfer is accepted,\ + \ it\ninitiates the car transfer process. Appropriate notifications are sent,\ + \ and\nactivity records are created for both acceptance and rejection actions.\n\ + \n:param request: The HTTP request object which contains metadata about\n \ + \ the request made by the user, including session and user information.\n:param\ + \ car_pk: The primary key of the car to be transferred.\n:param transfer_pk:\ + \ The primary key of the car transfer request to be processed.\n:return: An\ + \ HTTP redirect response to the 'inventory_stats' view." + source: inventory.views.car_transfer_accept_reject + type: view_function + car_transfer_approve: + description: 'Approves or cancels a car transfer request based on the action parameter. + This view + + handles the workflow of updating the transfer status and notifying the involved + parties + + accordingly. If the transfer is canceled, it reverts the car status to "available" + and + + deactivates the transfer record. If approved, it notifies the recipient dealer + and allows + + the request to proceed for further actions. + + + :param request: The HTTP request object containing metadata and the action parameter. + + :param car_pk: Primary key of the car involved in the transfer. + + :param transfer_pk: Primary key of the transfer request to approve or cancel. + + :return: An HTTP response redirecting to the car detail page of the specified + car.' + source: inventory.views.car_transfer_approve + type: view_function + create_estimate: + description: "Creates a new estimate based on the provided data and saves it.\ + \ This function processes\na POST request and expects a JSON payload containing\ + \ details of the estimate such as\ntitle, customer, terms, items, and quantities.\ + \ It validates the input data, ensures\navailability of stocks, and updates\ + \ or creates the corresponding estimate in the database.\n\nIf `pk` is provided,\ + \ it links the created estimate with an existing opportunity. It handles\nthe\ + \ reservation of cars and updates the stock information accordingly.\n\n:param\ + \ request: The HttpRequest object containing user-specific data and state.\n\ + :type request: HttpRequest\n:param pk: An optional primary key of the existing\ + \ opportunity to associate with\n the created estimate.\n:type pk:\ + \ int, optional\n:return: A JsonResponse object with status and either the created\ + \ quotation URL\n or an error message. If the request method is not\ + \ POST, it renders the\n estimate creation form.\n:rtype: JsonResponse\ + \ or HttpResponse" + source: inventory.views.create_estimate + type: view_function + create_sale_order: + description: "Creates a sale order for a given estimate and updates associated\ + \ item and car data.\n\nThis view is responsible for handling the submission\ + \ of a sale order form linked to\na specific estimate. It ensures that the estimate\ + \ is approved if not already, updates\nthe status of the related car items as\ + \ sold, and redirects to the estimate's detailed\nview upon successful creation\ + \ of the sale order. If the request method is not POST, it\nrenders the form\ + \ for the user to input sale order details, along with other contextual\ninformation\ + \ like estimate data and car finance details.\n\n:param request: HTTP request\ + \ object.\n:type request: HttpRequest\n:param pk: Primary key of the estimate\ + \ to create a sale order for.\n:type pk: int\n:return: An HTTP response rendering\ + \ the sale order form if the method is GET or invalid\n POST data, or redirects\ + \ to the estimate detail view upon successful creation.\n:rtype: HttpResponse" + source: inventory.views.create_sale_order + type: view_function + csrf_exempt: + description: Mark a view function as being exempt from the CSRF view protection. + source: rest_framework.views.csrf_exempt + type: view_function + custom_bad_request_view: + description: 'Handles custom bad request responses by rendering a specified HTML + + template for 400 status errors. + + + :return: Rendered HTTP response rendering the error page. + + :rtype: HttpResponse' + source: inventory.views.custom_bad_request_view + type: view_function + custom_error_view: + description: 'Handles rendering the custom error page for HTTP 500 errors. + + + This function is called when an unhandled exception occurs in the application. + It renders + + a predefined template for server errors, providing a consistent error page layout + + to the users. + + + :param request: The HTTP request instance which triggered the error. + + :type request: HttpRequest + + :param exception: The exception that caused the error to trigger. Defaults to + None. + + :type exception: Exception, optional + + :return: An HttpResponse object representing the rendered error page. + + :rtype: HttpResponse' + source: inventory.views.custom_error_view + type: view_function + custom_page_not_found_view: + description: 'Custom handler for 404 errors that renders a custom HTML page + + upon encountering a Page Not Found error. + + + :param request: The HttpRequest object associated with the request. + + :type request: HttpRequest + + :param exception: The exception that triggered the 404 error, if any. + + :type exception: Exception, optional + + :return: An HttpResponse object rendering the "errors/404.html" template. + + :rtype: HttpResponse' + source: inventory.views.custom_page_not_found_view + type: view_function + custom_permission_denied_view: + description: "Handles the custom view for 403 Forbidden permission denied errors.\ + \ This\nfunction renders a custom template for the error page when users are\ + \ denied\npermission to access a particular resource or view.\n\n:param request:\ + \ Django HttpRequest object containing metadata about the request.\n:type request:\ + \ HttpRequest\n:param exception: Optional exception that caused the 403 error.\n\ + \ Defaults to None.\n:type exception: Exception | None\n:return:\ + \ HttpResponse object rendering the 403.html error page.\n:rtype: HttpResponse" + source: inventory.views.custom_permission_denied_view + type: view_function + dealer_signup: + description: "Handles the dealer signup wizard process, including forms validation,\ + \ user and group\ncreation, permissions assignment, and dealer data storage.\ + \ This view supports GET\nrequests for rendering the signup wizard page, and\ + \ POST requests for processing\nsubmitted data. The function also differentiates\ + \ between requests sent with the\n\"Hx-Request\" header for partial form validations\ + \ in the wizard.\n\n:param request: The HTTP request object representing the\ + \ client request. It contains\n metadata about the request and the POST data\ + \ for creating the dealer.\n:type request: django.http.HttpRequest\n:param args:\ + \ Optional positional arguments passed to the view during the call.\n:type args:\ + \ tuple\n:param kwargs: Optional keyword arguments passed to the view during\ + \ the call.\n:type kwargs: dict\n:return: A rendered signup wizard page or a\ + \ JSON response indicating operation success\n or failure.\n:rtype: Union[django.http.HttpResponse,\ + \ django.http.JsonResponse]" + source: inventory.views.dealer_signup + type: view_function + decode: + description: "Decodes datamatrix barcodes in `image`.\n\nArgs:\n image: `numpy.ndarray`,\ + \ `PIL.Image` or tuple (pixels, width, height)\n symbols: iter(ZBarSymbol)\ + \ the symbol types to decode; if `None`, uses\n `zbar`'s default behaviour,\ + \ which is to decode all symbol types.\n\nReturns:\n :obj:`list` of :obj:`Decoded`:\ + \ The values decoded from barcodes." + source: inventory.views.decode + type: view_function + decodevin: + description: "Decodes a Vehicle Identification Number (VIN) using multiple decoding\ + \ functions\nand returns the decoded result. This function attempts to decode\ + \ the VIN using\nthree different functions in sequence and returns the first\ + \ successful result.\nIf none of the decoding functions produce a valid result,\ + \ the function returns None.\n\n:param vin: The Vehicle Identification Number\ + \ (VIN) to be decoded.\n:type vin: str\n:return: The decoded result if any decoding\ + \ function is successful, or None if\n all decoding attempts fail.\n\ + :rtype: dict | None" + source: api.views.decodevin + type: view_function + delete_customer: + description: 'Deletes a customer from the system and deactivates the corresponding + user account. + + + This function retrieves a customer object based on the primary key (pk), + + sets their active status to False, and deactivates the linked user account + + (using the email associated with the customer). After saving these changes, + + it displays a success message to the user and redirects to the customer list + page. + + + :param request: A HttpRequest object containing metadata about the request. + + :type request: HttpRequest + + :param pk: Primary key of the customer to be deleted. + + :type pk: int + + :return: A redirect response to the customer list page. + + :rtype: HttpResponseRedirect' + source: inventory.views.delete_customer + type: view_function + delete_note: + description: 'Deletes a specific note created by the currently logged-in user + and redirects + + to the lead detail page. If the note does not exist or the user is not the creator, + + a 404 error will be raised. A success message is displayed upon successful deletion + + of the note. + + + :param request: The HTTP request object associated with the current request. + + :type request: HttpRequest + + :param pk: The primary key of the note to be deleted. + + :type pk: int + + :return: An HTTP redirection to the lead detail page of the corresponding note''s + lead. + + :rtype: HttpResponseRedirect' + source: inventory.views.delete_note + type: view_function + delete_opportunity: + description: 'Deletes an opportunity object from the database and redirects to + the opportunity + + list view. If the opportunity with the specified primary key is not found, a + 404 + + error will be raised. Displays a success message after deletion. + + + :param request: The HTTP request object containing metadata about the request. + + :type request: HttpRequest + + :param pk: The primary key of the opportunity object to be deleted. + + :type pk: int + + :return: An HTTP response redirecting to the opportunity list view. + + :rtype: HttpResponse' + source: inventory.views.delete_opportunity + type: view_function + delete_vendor: + description: 'Deletes an existing vendor record from the database. + + + This function allows users with valid authentication to delete a specified + + vendor object by its primary key. Upon successful deletion, a success message + + is displayed, and the user is redirected to the vendor list page. + + + :param request: HttpRequest object containing metadata about the request. + + :type request: HttpRequest + + :param pk: Primary key of the vendor object to be deleted. + + :type pk: int + + :return: HttpResponseRedirect object for redirecting to the vendor list page. + + :rtype: HttpResponseRedirect' + source: inventory.views.delete_vendor + type: view_function + estimate_mark_as: + description: 'Marks an estimate with a specified status based on the requested + action and + + permissions. The marking possibilities include review, approval, rejection, + + completion, and cancellation. The function validates whether the estimate + + can transition to the desired status before updating it. It also handles + + notifications and updates related entities if required, such as car status + + changes upon cancellation. + + + :param request: The HTTP request object containing metadata about the request. + + :type request: HttpRequest + + :param pk: The primary key of the estimate to be marked. + + :type pk: int + + :return: A redirect response to the estimate detail view. + + :rtype: HttpResponseRedirect' + source: inventory.views.estimate_mark_as + type: view_function + get_car_finance_data: + description: "Fetches car finance data from the provided model instance and calculates\n\ + various related details including total prices, VAT, discounts, and\nadditional\ + \ services information.\n\n:param model: The model instance that contains car\ + \ and finance-related\n transaction data.\n:type model: Model\n:return: A\ + \ dictionary containing detailed information about the car finance\n transactions,\ + \ including individual car details, total quantity, calculated\n totals for\ + \ prices, VAT, discounts, additional services, and the VAT rate.\n:rtype: dict" + source: inventory.views.get_car_finance_data + type: view_function + get_item_transactions: + description: 'Extracts and compiles relevant transaction details from a list of + transactions, + + including information about cars, financing, estimates, and invoices. The extracted + + details for each transaction are stored as dictionaries in a list. + + + :param txs: A list of transaction objects from which information is extracted. + + :type txs: list + + :return: A list of dictionaries, each containing extracted transaction details. + + :rtype: list' + source: inventory.views.get_item_transactions + type: view_function + get_localdate: + description: "Fetches the current local date, optionally considering time zone\ + \ settings.\n\nThis function retrieves the current local date. If the global\ + \ settings indicate\nthe use of time zones (`USE_TZ` is True), the date is determined\ + \ based on the\nlocal time zone. Otherwise, the date is based on the system's\ + \ local time without\nany time zone consideration.\n\nReturns\n-------\ndate\n\ + \ The current local date, adjusted for the time zone setting if applicable." + source: django_ledger.views.get_localdate + type: view_function + get_make: + description: 'Fetches a car make object from the database based on the provided + item. The function + + first attempts an exact case-insensitive match on the full string. If no match + is found, + + it splits the input string by spaces and checks for a match with each fragment, + stopping + + as soon as a match is found. + + + :param item: A string representing the name or a part of the name of the car + make to search. + + :type item: str + + :return: The first matching CarMake object found, or None if no match is found. + + :rtype: CarMake or None' + source: inventory.views.get_make + type: view_function + get_model: + description: "Searches for a car model associated with a specific make by performing\ + \ an\nexact case-insensitive match. If no match is found, it attempts to match\n\ + based on individual words split from the input item.\n\n:param item: A string\ + \ representing the name of the car model to search for.\n:param make: An object\ + \ representing the car manufacturer, which contains the\n associated car\ + \ models.\n:return: Returns the first car model object that matches the search\ + \ criteria,\n or None if no match is found." + source: inventory.views.get_model + type: view_function + get_object_or_404: + description: 'Use get() to return an object, or raise an Http404 exception if + the object + + does not exist. + + + klass may be a Model, Manager, or QuerySet object. All other passed + + arguments and keyword arguments are used in the get() query. + + + Like with QuerySet.get(), MultipleObjectsReturned is raised if more than + + one object is found.' + source: plans.views.get_object_or_404 + type: view_function + get_user_type: + description: "Determine the type of user based on the given request object.\n\n\ + This function identifies the type of user from the provided request. It\nchecks\ + \ if the user is a dealer, staff member, or neither based on the\nattributes\ + \ of the request object, returning the appropriate associated\nuser type or\ + \ `None` if the determination cannot be made.\n\n:param request: The HTTP request\ + \ object containing user and role\n information.\n:type request: Any\n:return:\ + \ A dealer object if the user is a dealer, a dealer object\n associated with\ + \ the staff member if the user is a staff member, or\n None if the user type\ + \ cannot be identified.\n:rtype: Optional[Any]" + source: api.views.get_user_type + type: view_function + inventory_stats_view: + description: "Handle the inventory stats view for a dealer, displaying detailed\ + \ information\nabout the cars, including counts grouped by make, model, and\ + \ trim.\n\nThe function fetches all cars associated with the authenticated dealer,\ + \ calculates\nthe inventory statistics (e.g., total cars, reserved cars, and\ + \ cars categorized\nby make, model, and trim levels), and prepares the data\ + \ to be rendered in a\ntemplate.\n\n:param request: The HTTP request object\ + \ from the client.\n:type request: HttpRequest\n:return: An HTTP response containing\ + \ structured inventory data rendered in the\n \"inventory/inventory_stats.html\"\ + \ template.\n:rtype: HttpResponse" + source: inventory.views.inventory_stats_view + type: view_function + invoice_create: + description: "Handles the creation of a new invoice associated with a given estimate.\ + \ It validates\nthe submitted data through a form, processes the invoice, updates\ + \ related models, and\nfinalizes the estimate. If successful, redirects to the\ + \ detailed view of the created\ninvoice. If the submitted data is invalid or\ + \ the request is not a POST request, renders\nthe invoice creation form.\n\n\ + :param request: The HTTP request object.\n:type request: HttpRequest\n:param\ + \ pk: The primary key of the estimate associated with the invoice.\n:type pk:\ + \ int\n:return: An HTTP response. Redirects to the \"invoice_detail\" view upon\ + \ successful invoice\n creation or renders the invoice creation form template\ + \ otherwise.\n:rtype: HttpResponse" + source: inventory.views.invoice_create + type: view_function + invoice_mark_as: + description: 'Marks an invoice as approved if it meets the required conditions. + + + This view is responsible for marking an invoice as approved based on the provided + + `mark` parameter. If the `mark` parameter is specified as "accept" and the invoice + + is eligible for approval, it gets approved and saved. Otherwise, an error message + + is displayed. The function requires the user to be logged in and to have the + + appropriate permission to change the InvoiceModel. + + + :param request: The HTTP request object containing metadata about the request. + + :type request: django.http.HttpRequest + + :param pk: The primary key of the invoice to be processed. + + :type pk: int + + :return: An HTTP redirect response to the invoice detail page after processing. + + :rtype: django.http.HttpResponse' + source: inventory.views.invoice_mark_as + type: view_function + lead_convert: + description: 'Converts a lead into a customer and creates a corresponding opportunity. + + + The function ensures that leads are not converted to customers more than once. + + If the lead has already been converted, an error is displayed. Otherwise, + + the lead is converted into a customer and a new opportunity is created. + + Upon successful conversion, the user is redirected to the lead list view + + and a success message is displayed. + + + :param request: The HTTP request object representing the user''s request. + + :type request: HttpRequest + + :param pk: The primary key of the lead to be converted. + + :type pk: int + + :return: An HTTP response redirecting to the lead list view. + + :rtype: HttpResponse' + source: inventory.views.lead_convert + type: view_function + lead_create: + description: 'Handles the creation of a new lead in the inventory system. + + + This function manages the rendering and processing of a form for creating a + new + + lead. It filters options for car models in the form based on the selected car + + make. For POST requests, it validates and processes the submitted form data + + and creates the lead if valid. It also creates a corresponding customer in the + + ledger system if one does not exist for the provided email. + + + :param request: The HTTP request object containing request data. + + :type request: HttpRequest + + + :return: An HTTP response object rendering the lead creation form or redirecting + to the lead list page upon success. + + :rtype: HttpResponse' + source: inventory.views.lead_create + type: view_function + lead_transfer: + description: 'Handles the transfer of a lead to a different staff member. This + view is accessible + + only to authenticated users with the ''inventory.change_lead'' permission. If + the + + request method is POST and the form data is valid, the lead''s staff is updated + + accordingly, saved, and a success message is displayed. Otherwise, an error + message + + is shown. In both cases, the user is redirected to the lead listing page. + + + :param request: The HTTP request object. + + :param pk: The primary key of the lead to be transferred. + + :return: An HTTP redirect response to the lead list view.' + source: inventory.views.lead_transfer + type: view_function + ledger_lock_all_journals: + description: 'Locks all journals associated with a specific ledger. If the ledger + is already locked, + + it will notify the user through an error message. Otherwise, it initiates the + locking of + + related journal entries, locks the ledger itself, and saves the changes to the + database. + + After the operation, it redirects the user to the journal entry list associated + with the + + ledger. + + + :param request: HttpRequest object representing the current request. + + :type request: HttpRequest + + :param entity_slug: The slug identifier of the entity. + + :type entity_slug: str + + :param pk: The primary key of the ledger to be locked. + + :type pk: int + + :return: HttpResponse redirecting to the journal entry list page of the locked + ledger. + + :rtype: HttpResponse' + source: inventory.views.ledger_lock_all_journals + type: view_function + ledger_post_all_journals: + description: 'Posts all journal entries associated with a ledger. This function + updates the ledger''s + + state to reflect that its journal entries have been posted. If the ledger is + already + + posted, an error message is displayed and the user is redirected. + + + :param request: The HTTP request object used for processing the action. + + :type request: HttpRequest + + :param entity_slug: A string representing the specific entity slug for the ledger + context. + + :type entity_slug: str + + :param pk: The primary key of the ledger to be posted. + + :type pk: int + + :return: A redirect to the journal entry list view for the specified ledger. + + :rtype: HttpResponseRedirect' + source: inventory.views.ledger_post_all_journals + type: view_function + ledger_unlock_all_journals: + description: 'Unlocks all journal entries associated with a specific ledger. This + function first checks if the + + ledger is locked. If it is already unlocked, it shows an error message and redirects + the user + + to the journal entry list page. If the ledger is locked, it unlocks the ledger, + saves changes, + + and iterates through all the locked journal entries within the ledger to unlock + them as well. + + Afterward, it redirects the user to the journal entry list page. + + + :param request: The HTTP request object containing user session and request + metadata. + + :type request: HttpRequest + + :param entity_slug: A unique string slug representing the specific entity or + organization context. + + :type entity_slug: str + + :param pk: The primary key of the ledger record to be unlocked. + + :type pk: int + + :return: A redirection to the journal entry list page for the specified ledger. + + :rtype: HttpResponseRedirect' + source: inventory.views.ledger_unlock_all_journals + type: view_function + ledger_unpost_all_journals: + description: "Unposts all journal entries for a specified ledger and marks the\ + \ ledger as unposted.\n\nThis function identifies a ledger by its primary key\ + \ (pk) and checks if it is\nalready posted. If the ledger is not posted, an\ + \ error message is displayed.\nIf it is posted, the function iterates through\ + \ its posted journal entries,\nmarks them as unposted, and saves the changes.\ + \ Finally, it marks the ledger itself\nas unposted and saves it.\n\n:param request:\ + \ The HTTP request object from the client.\n:type request: HttpRequest\n:param\ + \ entity_slug: A slug identifying the entity associated with the ledger.\n:type\ + \ entity_slug: str\n:param pk: The primary key of the ledger whose journal entries\ + \ are being unposted.\n:type pk: int\n:return: An HTTP redirect response object\ + \ directing the client to the journal entry list\n page for the specified\ + \ ledger.\n:rtype: HttpResponseRedirect" + source: inventory.views.ledger_unpost_all_journals + type: view_function + manage_reservation: + description: "Handles the management of a car reservation, providing options to\ + \ renew or\ncancel an existing reservation associated with the logged-in user.\n\ + \nRenewing a reservation extends the reservation period by an additional\n24\ + \ hours. Canceling a reservation deletes it and updates the car's status\nto\ + \ AVAILABLE. All actions require a valid reservation and are performed\nbased\ + \ on the current user's authentication and request type.\n\n:param request:\ + \ Django HttpRequest object representing the client's request.\n:type request:\ + \ HttpRequest\n:param reservation_id: The unique identifier of the car reservation\ + \ to manage.\n:type reservation_id: int\n:return: On POST requests, returns\ + \ an HTTP redirect or JSON response\n based on the action performed. On other\ + \ request methods,\n returns a JSON response with an error message.\n:rtype:\ + \ JsonResponse or HttpResponseRedirect" + source: inventory.views.manage_reservation + type: view_function + opportunity_update_status: + description: "Update the status and/or stage of a specific Opportunity instance.\ + \ This is a\nview function, which is generally tied to a URL endpoint in a Django\ + \ application.\nThe function is accessible only to authenticated users due to\ + \ the\n`@login_required` decorator.\n\nThe function retrieves the `Opportunity`\ + \ instance based on the primary key\nin the URL, updates the status or stage\ + \ of the instance based on query\nparameters in the request, saves the changes,\ + \ and then redirects to the\ndetail view of the `Opportunity`. Additionally,\ + \ a success message is displayed,\nand a custom header (`HX-Refresh`) is added\ + \ to the response.\n\n:param request: The HTTP request object containing details\ + \ of the user's\n request, such as query parameters for status and stage.\ + \ This is a\n mandatory parameter provided by Django's view function framework.\n\ + :type request: HttpRequest\n:param pk: The primary key of the `Opportunity`\ + \ instance to be updated.\n:type pk: int\n\n:return: An HTTP response object\ + \ that redirects to the updated Opportunity's\n detail page, with a success\ + \ message and the \"HX-Refresh\" header to trigger\n frontend behavior.\n\ + :rtype: HttpResponse" + source: inventory.views.opportunity_update_status + type: view_function + payment_mark_as_paid: + description: "Marks an invoice as paid if it meets the conditions of being fully\ + \ paid and eligible\nfor payment. Also ensures that related ledger journal entries\ + \ are locked and posted\nwhen the payment is marked successfully.\n\nThis function\ + \ is protected with both `login_required` and\n`permission_required` decorators,\ + \ ensuring that only logged-in users with\nappropriate permissions can execute\ + \ it.\n\n:param request: HttpRequest object containing metadata about the request.\n\ + :type request: HttpRequest\n:param pk: Primary key of the invoice to mark as\ + \ paid.\n:type pk: int\n:return: Redirect response to the invoice detail page.\n\ + :rtype: HttpResponseRedirect\n:raises: In case of an exception during the process,\ + \ an error message is\n displayed to the user through Django's messaging\ + \ framework." + source: inventory.views.payment_mark_as_paid + type: view_function + permission_required: + description: 'Decorator for views that checks whether a user has a particular + permission + + enabled, redirecting to the log-in page if necessary. + + If the raise_exception parameter is given the PermissionDenied exception + + is raised.' + source: inventory.views.permission_required + type: view_function + preview_sale_order: + description: 'Handles rendering of the sale order preview page for a specific + estimate. + + + This view retrieves an `EstimateModel` object based on the provided primary + + key (`pk`), fetches related car finance data, and renders the preview of the + + sale order associated with the given estimate. + + + :param request: The HTTP request object + + :type request: HttpRequest + + :param pk: The primary key of the `EstimateModel` to retrieve + + :type pk: int + + :return: HTTP response containing the rendered sale order preview page + + :rtype: HttpResponse' + source: inventory.views.preview_sale_order + type: view_function + redirect: + description: "Return an HttpResponseRedirect to the appropriate URL for the arguments\n\ + passed.\n\nThe arguments could be:\n\n * A model: the model's `get_absolute_url()`\ + \ function will be called.\n\n * A view name, possibly with arguments: `urls.reverse()`\ + \ will be used\n to reverse-resolve the name.\n\n * A URL, which will\ + \ be used as-is for the redirect location.\n\nIssues a temporary redirect by\ + \ default. Set permanent=True to issue a\npermanent redirect. Set preserve_request=True\ + \ to instruct the user agent\nto preserve the original HTTP method and body\ + \ when following the redirect." + source: appointment.views.redirect + type: view_function + render: + description: 'Return an HttpResponse whose content is filled with the result of + calling + + django.template.loader.render_to_string() with the passed arguments.' + source: django_pdf_actions.views.render + type: view_function + reserve_car: + description: 'Reserve a car for a user for 24 hours and update its status to reserved. + + + The function creates a reservation record for the specified car, sets the + + reservation expiration time, updates the car''s status, and notifies the user + + about the operation outcome through success or error messages. It then redirects + + to the car detail page. + + + :param car: The car object to be reserved. + + :param request: The HTTP request object containing the user making the reservation. + + :return: Redirection to the car''s detail page.' + source: inventory.views.reserve_car + type: view_function + reserve_car_view: + description: 'Handles car reservation requests. This view requires the user to + be logged in + + and processes only POST requests. When invoked, it checks if the specified car + + is already reserved. If not, it proceeds to reserve the car for the user and + + sends an appropriate response. If the car is already reserved or if the request + + method is invalid, it provides corresponding error messages or responses. + + + :param request: The HTTP request object. + + :type request: HttpRequest + + :param car_id: The unique identifier of the car to be reserved. + + :type car_id: int + + :return: A response indicating the result of the reservation process. + + :rtype: HttpResponse or JsonResponse' + source: inventory.views.reserve_car_view + type: view_function + sales_list_view: + description: "Handles the retrieval and presentation of a paginated list of item\ + \ transactions for\nsales, specific to the logged-in user's entity. Requires\ + \ the user to have appropriate\npermissions to view the list.\n\n:param request:\ + \ The HTTP request object containing metadata about the request,\n such as\ + \ HTTP method, user credentials, and sent data.\n:type request: HttpRequest\n\ + \n:return: An HTTP response with the rendered sales list page containing the\ + \ paginated\n item transactions specific to the user's entity.\n:rtype: HttpResponse" + source: inventory.views.sales_list_view + type: view_function + schedule_cancel: + description: 'Cancel a schedule by updating its status to "Canceled". The function + is protected + + by a login requirement, ensuring only authenticated users can execute it. It + + retrieves the schedule object by its primary key, updates its status, saves + the + + changes, and returns an HTTP response with status code 200. + + + :param request: The HTTP request object representing the user''s request. + + :type request: HttpRequest + + :param pk: The primary key of the schedule to be canceled. + + :type pk: int + + :return: An HTTP response object with a 200 status code upon successful execution. + + :rtype: HttpResponse' + source: inventory.views.schedule_cancel + type: view_function + schedule_lead: + description: "Handles the scheduling of a lead for an appointment.\n\nThis function\ + \ ensures that only staff members with the appropriate permissions\ncan schedule\ + \ leads. If the user is not a staff member or does not have the\nrequired inventory\ + \ permissions, they are redirected with an appropriate error\nmessage. The function\ + \ creates an appointment request and an associated appointment\nrecord upon\ + \ successful submission of the scheduling form.\n\n:param request: The HTTP\ + \ request object containing metadata about the request.\n:type request: HttpRequest\n\ + :param pk: The primary key of the lead to be scheduled.\n:type pk: int\n:return:\ + \ A rendered template or a redirection to another view based on the request\n\ + \ method and validity of the form submission.\n:rtype: HttpResponse" + source: inventory.views.schedule_lead + type: view_function + send_email_view: + description: 'View function to send an email for an estimate. This function allows + authenticated and + + authorized users to send an email containing the estimate details to the customer. + + The email includes a link to preview the estimate and a message template in + multiple + + languages. If the estimate does not have any associated items, the user will + receive + + an error message. Upon successfully sending the email, the estimate is marked + as reviewed. + + + :param request: The HttpRequest object containing metadata about the request. + + :type request: HttpRequest + + :param pk: The primary key of the estimate to be sent via email. + + :type pk: int + + :return: An HttpResponseRedirect to the estimate detail view. + + :rtype: HttpResponseRedirect + + :raises Http404: If the estimate with the given primary key does not exist.' + source: inventory.views.send_email_view + type: view_function + send_lead_email: + description: "Handles sending emails related to a lead. Supports creating drafts,\ + \ sending emails, and rendering\nan email composition page. Changes on the lead\ + \ or email objects, such as marking a lead as contacted\nor an email as sent,\ + \ are recorded in associated activity logs.\n\n:param request: The HTTP request\ + \ object. This contains user information, method type, and data such as\n \ + \ GET or POST payload used to draft or send the email appropriately.\n\ + \ Type: HttpRequest\n:param pk: The primary key of the lead to\ + \ which the email action is associated. It's used to retrieve\n the\ + \ lead object from the database.\n Type: int\n:param email_pk: Optional\ + \ parameter representing the primary key of an email template. When provided,\n\ + \ the respective email content is pre-populated into the email\ + \ composition form.\n Defaults to None.\n Type:\ + \ Optional[int]\n:return: When successfully sending an email, redirects the\ + \ user to the lead list page. On draft mode\n or email composition rendering,\ + \ a response object is returned to render the respective page.\n Type:\ + \ HttpResponse" + source: inventory.views.send_lead_email + type: view_function + set_bill_payment: + description: 'Sets the payment for a given bill by creating journal entries for + the + + transaction and updating the respective accounts and the bill''s status. + + + The function handles the transaction by creating a new journal entry + + linked with the specified bill, then records debit and credit + + transactions using the entity’s cash and accounts payable accounts. + + It finally updates the bill''s payment status and persists changes. + + + :param dealer: The dealer making or receiving the payment. + + :type dealer: Any + + :param entity: The business entity involved in the payment transaction. + + :type entity: Any + + :param bill: The bill object representing the invoice to be paid. + + :type bill: Any + + :param amount: The amount to be paid for the bill. + + :type amount: Decimal + + :param payment_method: The method used to make the payment (e.g., cash, credit). + + :type payment_method: Any + + :return: None' + source: inventory.views.set_bill_payment + type: view_function + set_invoice_payment: + description: 'Processes and applies a payment for a specified invoice. This function + calculates + + finance details, handles associated account transactions, and updates the invoice + + status accordingly. + + + :param dealer: Dealer object responsible for processing the payment + + :type dealer: Dealer + + :param entity: Entity object associated with the invoice and payment + + :type entity: Entity + + :param invoice: The invoice object for which the payment is being made + + :type invoice: Invoice + + :param amount: The amount being paid towards the invoice + + :type amount: Decimal + + :param payment_method: The payment method used for the transaction + + :type payment_method: str + + :return: None' + source: inventory.views.set_invoice_payment + type: view_function + switch_language: + description: "Switches the current language context for the user based on a request\ + \ parameter, modifies the URL path\naccordingly, and updates session and cookies\ + \ with the new language preference.\n\n:param request: The HTTP request object\ + \ containing information about the user request, including\n \ + \ the desired language to switch to and the referring URL.\n \ + \ - \"GET\" dictionary is accessed to retrieve the desired language parameter.\n\ + \ - \"META\" dictionary is used to extract the referring URL\ + \ via \"HTTP_REFERER\".\n\n:return: A redirect response object pointing to the\ + \ modified URL with the updated language\n preference, if the requested\ + \ language is valid. Otherwise, redirects to the default URL." + source: inventory.views.switch_language + type: view_function + urlparse: + description: 'Parse a URL into 6 components: + + :///;?# + + + The result is a named 6-tuple with fields corresponding to the + + above. It is either a ParseResult or ParseResultBytes object, + + depending on the type of the url parameter. + + + The username, password, hostname, and port sub-components of netloc + + can also be accessed as attributes of the returned object. + + + The scheme argument provides the default value of the scheme + + component when no scheme is found in url. + + + If allow_fragments is False, no attempt is made to separate the + + fragment component from the previous component, which can be either + + path or query. + + + Note that % escapes are not expanded.' + source: inventory.views.urlparse + type: view_function + urlunparse: + description: 'Put a parsed URL back together again. This may result in a + + slightly different, but equivalent URL, if the URL that was parsed + + originally had redundant delimiters, e.g. a ? with an empty query + + (the draft states that these are equivalent).' + source: inventory.views.urlunparse + type: view_function + vendorDetailView: + description: 'Fetches and renders the detail view for a specific vendor. + + + This function retrieves a vendor object based on the primary key (pk) + + provided in the URL, ensures the user is logged in to access the + + view, and renders the vendor detail template with the vendor''s context. + + + :param request: The HTTP request object. + + :type request: HttpRequest + + :param pk: The primary key of the vendor to retrieve. + + :type pk: int + + :return: An HttpResponse object containing the rendered vendor detail page. + + :rtype: HttpResponse' + source: inventory.views.vendorDetailView + type: view_function + sync_user_email_addresses: + description: 'Keep user.email in sync with user.emailaddress_set. + + + Under some circumstances the user.email may not have ended up as + + an EmailAddress record, e.g. in the case of manually created admin + + users.' + source: allauth.account.views.sync_user_email_addresses + type: view_function + escape: + description: 'Return the given text with ampersands, quotes and angle brackets + encoded + + for use in HTML. + + + Always escape input, even if it''s already escaped and marked as such. + + This may result in double-escaping. If this is a concern, use + + conditional_escape() instead.' + source: debug_toolbar.views.escape + type: view_function + render_panel: + description: Render the contents of a panel + source: debug_toolbar.views.render_panel + type: view_function + render_with_toolbar_language: + description: Force any rendering within the view to use the toolbar's language. + source: debug_toolbar.views.render_with_toolbar_language + type: view_function + require_show_toolbar: + description: 'Async compatible decorator to restrict access to a view + + based on the Debug Toolbar''s visibility settings.' + source: debug_toolbar.views.require_show_toolbar + type: view_function + exception_handler: + description: 'Returns the response that should be used for any given exception. + + + By default we handle the REST framework `APIException`, and also + + Django''s built-in `Http404` and `PermissionDenied` exceptions. + + + Any unhandled exceptions may return `None`, which will cause a 500 error + + to be raised.' + source: rest_framework.views.exception_handler + type: view_function + get_view_description: + description: 'Given a view instance, return a textual description to represent + the view. + + This name is used in the browsable API, and in OPTIONS responses. + + + This function is the default for the `VIEW_DESCRIPTION_FUNCTION` setting.' + source: rest_framework.views.get_view_description + type: view_function + get_view_name: + description: 'Given a view instance, return a textual name to represent the view. + + This name is used in the browsable API, and in OPTIONS responses. + + + This function is the default for the `VIEW_NAME_FUNCTION` setting.' + source: rest_framework.views.get_view_name + type: view_function + patch_vary_headers: + description: 'Add (or update) the "Vary" header in the given HttpResponse object. + + newheaders is a list of header names that should be in "Vary". If headers + + contains an asterisk, then "Vary" header will consist of a single asterisk + + ''*''. Otherwise, existing headers in "Vary" aren''t removed.' + source: rest_framework.views.patch_vary_headers + type: view_function + smart_str: + description: 'Return a string representing ''s''. Treat bytestrings using the + ''encoding'' + + codec. + + + If strings_only is True, don''t convert (some) non-string-like objects.' + source: rest_framework.views.smart_str + type: view_function + add_message: + description: Attempt to add a message to the request using the 'messages' app. + source: django_ledger.views.add_message + type: view_function + format_html: + description: 'Similar to str.format, but pass all arguments through conditional_escape(), + + and call mark_safe() on the result. This function should be used instead + + of str.format or % interpolation to build up small HTML fragments.' + source: django_ledger.views.format_html + type: view_function + get_localtime: + description: "Retrieve the local time based on the specified timezone.\n\nDetermines\ + \ the local time depending on whether timezone support (``USE_TZ``) is\nenabled\ + \ in global settings. If timezone support is enabled, it uses the\n`localtime`\ + \ function to obtain the local time according to the provided\ntimezone. If\ + \ timezone support is disabled, it defaults to the current time\nwith respect\ + \ to the given timezone.\n\nParameters\n----------\ntz : timezone or None, optional\n\ + \ The timezone to determine the local time. If `None`, defaults to the system\n\ + \ timezone.\n\nReturns\n-------\ndatetime\n A datetime object representing\ + \ the calculated local time." + source: django_ledger.views.get_localtime + type: view_function + make_aware: + description: Make a naive datetime.datetime in a given time zone aware. + source: django_ledger.views.make_aware + type: view_function + analyze_prompt_sync: + description: "Synchronous function to analyze a prompt and return insights.\n\ + Perfect for Django views.\n\nArgs:\n prompt: Natural language query\n \ + \ **kwargs: Additional parameters for InsightRequest\n\nReturns:\n Dictionary\ + \ with query results" + source: haikalbot.views.analyze_prompt_sync + type: view_function + export_to_csv: + description: "Export data to CSV format.\n\nArgs:\n data: Data to export\n\ + \ filename: Base filename without extension\n\nReturns:\n HttpResponse:\ + \ Response with CSV file" + source: haikalbot.views.export_to_csv + type: view_function + export_to_excel: + description: "Export data to Excel format.\n\nArgs:\n data: Data to export\n\ + \ filename: Base filename without extension\n\nReturns:\n HttpResponse:\ + \ Response with Excel file" + source: haikalbot.views.export_to_excel + type: view_function + appointment_client_information: + description: 'This view function handles client information submission for an + appointment. + + + :param request: The request instance. + + :param appointment_request_id: The ID of the appointment request. + + :param id_request: The unique ID of the appointment request. + + :return: The rendered HTML page.' + source: appointment.views.appointment_client_information + type: view_function + appointment_request: + description: 'This view function handles requests to book an appointment for a + service. + + + :param request: The request instance. + + :param service_id: The ID of the service. + + :param staff_member_id: The ID of the staff member. + + :return: The rendered HTML page.' + source: appointment.views.appointment_request + type: view_function + appointment_request_submit: + description: 'This view function handles the submission of the appointment request + form. + + + :param request: The request instance. + + :return: The rendered HTML page.' + source: appointment.views.appointment_request_submit + type: view_function + check_day_off_for_staff: + description: 'Check if the given staff member is off on the given date. + + :param staff_member: The staff member to check. + + :param date: The date to check.' + source: appointment.views.check_day_off_for_staff + type: view_function + check_q_cluster: + description: 'Checks if Django Q is properly installed and configured in the Django + settings. + + If ''django_q'' is not in INSTALLED_APPS, it warns about both ''django_q'' not + being installed + + and ''Q_CLUSTER'' likely not being configured. + + If ''django_q'' is installed but ''Q_CLUSTER'' is not configured, it only warns + about ''Q_CLUSTER''. + + Returns True if configurations are correct, otherwise False.' + source: appointment.views.check_q_cluster + type: view_function + convert_str_to_date: + description: "Convert a date string to a datetime date object.\n\n:param date_str:\ + \ The date string.\n Supported formats include `%Y-%m-%d` (like\ + \ \"2023-12-31\") and `%Y/%m/%d` (like \"2023/12/31\").\n:return: The converted\ + \ `datetime.date`'s object." + source: appointment.views.convert_str_to_date + type: view_function + create_and_save_appointment: + description: 'Create and save a new appointment based on the provided appointment + request and client data. + + + :param ar: The appointment request associated with the new appointment. + + :param client_data: The data of the client making the appointment. + + :param appointment_data: Additional data for the appointment, including phone + number, address, etc. + + :param request: The request object. + + :return: The newly created appointment.' + source: appointment.views.create_and_save_appointment + type: view_function + create_appointment: + description: 'This function creates a new appointment and redirects to the payment + page or the thank-you page. + + + :param request: The request instance. + + :param appointment_request_obj: The AppointmentRequest instance. + + :param client_data: The client data. + + :param appointment_data: The appointment data. + + :return: The redirect response.' + source: appointment.views.create_appointment + type: view_function + create_payment_info_and_get_url: + description: 'Create a new payment information entry for the appointment and return + the payment URL. + + + :param appointment: The appointment to create the payment information for. + + :return: The payment URL for the appointment.' + source: appointment.views.create_payment_info_and_get_url + type: view_function + default_thank_you: + description: 'This view function handles the default thank you page. + + + :param request: The request instance. + + :param appointment_id: The ID of the appointment. + + :return: The rendered HTML page.' + source: appointment.views.default_thank_you + type: view_function + enter_verification_code: + description: 'This view function handles the submission of the email verification + code. + + + :param request: The request instance. + + :param appointment_request_id: The ID of the appointment request. + + :param id_request: The unique ID of the appointment request. + + :return: The rendered HTML page.' + source: appointment.views.enter_verification_code + type: view_function + force_str: + description: 'Similar to smart_str(), except that lazy instances are resolved + to + + strings, rather than kept as lazy objects. + + + If strings_only is True, don''t convert (some) non-string-like objects.' + source: appointment.views.force_str + type: view_function + get_appointment_data_from_session: + description: 'Get the appointment data from the session variables. + + Retrieves the phone, want_reminder, address, and additional_info stored in the + session. + + + :param request: The Django HttpRequest object. + + :return: The appointment data retrieved from the session.' + source: appointment.views.get_appointment_data_from_session + type: view_function + get_appointments_and_slots: + description: "Get appointments and available slots for a given date and service.\n\ + \nIf a service is provided, the function retrieves appointments for that service\ + \ on the given date.\nOtherwise, it retrieves all appointments for the given\ + \ date.\n\n:param date_: datetime.date, the date for which to retrieve appointments\ + \ and available slots\n:param service: Service, the service for which to retrieve\ + \ appointments\n:return: tuple, a tuple containing two elements:\n - A queryset\ + \ of appointments for the given date and service (if provided).\n - A list\ + \ of available time slots on the given date, excluding booked appointments." + source: appointment.views.get_appointments_and_slots + type: view_function + get_available_slots_ajax: + description: 'This view function handles AJAX requests to get available slots + for a selected date. + + + :param request: The request instance. + + :return: A JSON response containing available slots, selected date, an error + flag, and an optional error message.' + source: appointment.views.get_available_slots_ajax + type: view_function + get_available_slots_for_staff: + description: 'Calculate the available time slots for a given date and a staff + member. + + + :param date: The date for which to calculate the available slots + + :param staff_member: The staff member for which to calculate the available slots + + :return: A list of available time slots as strings in the format ''%I:%M %p'' + like [''10:00 AM'', ''10:30 AM'']' + source: appointment.views.get_available_slots_for_staff + type: view_function + get_current_timezone_name: + description: Return the name of the currently active time zone. + source: appointment.views.get_current_timezone_name + type: view_function + get_generic_context_with_extra: + description: Get the generic context for the admin pages with extra context. + source: appointment.views.get_generic_context_with_extra + type: view_function + get_locale: + description: 'Get the current locale based on the user''s language settings, without + the country code. + + Used in the JavaScript files. + + Can''t use the lang_country format because it is not supported. + + + :return: The current locale as a string (language code only)' + source: appointment.views.get_locale + type: view_function + get_next_available_date_ajax: + description: 'This view function handles AJAX requests to get the next available + date for a service. + + + :param request: The request instance. + + :param service_id: The ID of the service. + + :return: A JSON response containing the next available date.' + source: appointment.views.get_next_available_date_ajax + type: view_function + get_non_working_days_for_staff: + description: Return the non-working days for the given staff member or an empty + list if the staff member does not exist. + source: appointment.views.get_non_working_days_for_staff + type: view_function + get_user_by_email: + description: 'Get a user by their email address. + + + :param email: The email address of the user. + + :return: The user with the specified email address, if found; otherwise, None.' + source: appointment.views.get_user_by_email + type: view_function + get_website_name: + description: 'Get the website name from the configuration file. + + + :return: The website name' + source: appointment.views.get_website_name + type: view_function + get_weekday_num_from_date: + description: Get the number of the weekday from the given date. + source: appointment.views.get_weekday_num_from_date + type: view_function + handle_existing_email: + description: 'Handle the case where the email already exists in the database. + + + Sends a verification email to the existing user and redirects the client to + enter the verification code. + + + If the email is already in the session variables, clean the session variables + for email, phone, want_reminder, + + address, and additional_info. Then, store the current email, phone, want_reminder, + address, and additional_info + + in the session. + + + :param request: The Django HttpRequest object. + + :param client_data: The data of the client for the appointment. + + :param appointment_data: The data of the appointment. + + :param appointment_request_id: The ID of the appointment request. + + :param id_request: The unique ID for the appointment request. + + :return: The redirect response to enter the verification code.' + source: appointment.views.handle_existing_email + type: view_function + has_required_email_settings: + description: Check if all required email settings are configured and warn if any + are missing. + source: appointment.views.has_required_email_settings + type: view_function + is_working_day: + description: Check if the given day is a working day for the staff member. + source: appointment.views.is_working_day + type: view_function + json_response: + description: Return a generic JSON response. + source: appointment.views.json_response + type: view_function + login: + description: 'Persist a user id and a backend in the request. This way a user + doesn''t + + have to reauthenticate on every request. Note that data set during + + the anonymous session is retained when the user logs in.' + source: appointment.views.login + type: view_function + notify_admin_about_appointment: + description: Notify the admin and the staff member about a new appointment request. + source: appointment.views.notify_admin_about_appointment + type: view_function + notify_admin_about_reschedule: + description: Notify the admin and the staff member about a rescheduled appointment + request. + source: appointment.views.notify_admin_about_reschedule + type: view_function + redirect_to_payment_or_thank_you_page: + description: 'This function redirects to the payment page or the thank-you page + based on the configuration. + + + :param appointment: The Appointment instance. + + :return: The redirect response.' + source: appointment.views.redirect_to_payment_or_thank_you_page + type: view_function + require_ajax: + description: 'Decorator to require a request to be AJAX. + + Usage: @require_ajax' + source: appointment.views.require_ajax + type: view_function + send_reschedule_confirmation_email: + description: Send a rescheduling confirmation email to the client. + source: appointment.views.send_reschedule_confirmation_email + type: view_function + send_thank_you_email: + description: 'Send a thank-you email to the client for booking an appointment. + + + :param ar: The appointment request associated with the booking. + + :param user: The user who booked the appointment. + + :param email: The email address of the client. + + :param appointment_details: Additional details about the appointment (default + None). + + :param account_details: Additional details about the account (default None). + + :param request: The request object. + + :return: None' + source: appointment.views.send_thank_you_email + type: view_function + verify_user_and_login: + description: 'This function verifies the user''s email and logs the user in. + + + :param request: The request instance. + + :param user: The User instance. + + :param code: The verification code.' + source: appointment.views.verify_user_and_login + type: view_function + import_name: + description: import module given by str or pass the module if it is not str + source: plans.views.import_name + type: view_function + plan_validation: + description: 'Validates validator that represents quotas in a given system + + :param user: + + :param plan: + + :return:' + source: plans.views.plan_validation + type: view_function + Site: + description: Site(id, domain, name) + source: sites.models.Site + type: model_class + LogEntry: + description: LogEntry(id, action_time, user, content_type, object_id, object_repr, + action_flag, change_message) + source: admin.models.LogEntry + type: model_class + Permission: + description: "The permissions system provides a way to assign permissions to specific\n\ + users and groups of users.\n\nThe permission system is used by the Django admin\ + \ site, but may also be\nuseful in your own code. The Django admin site uses\ + \ permissions as follows:\n\n - The \"add\" permission limits the user's\ + \ ability to view the \"add\" form\n and add an object.\n - The \"change\"\ + \ permission limits a user's ability to view the change\n list, view the\ + \ \"change\" form and change an object.\n - The \"delete\" permission limits\ + \ the ability to delete an object.\n - The \"view\" permission limits the\ + \ ability to view an object.\n\nPermissions are set globally per type of object,\ + \ not per specific object\ninstance. It is possible to say \"Mary may change\ + \ news stories,\" but it's\nnot currently possible to say \"Mary may change\ + \ news stories, but only the\nones she created herself\" or \"Mary may only\ + \ change news stories that have a\ncertain status or publication date.\"\n\n\ + The permissions listed above are automatically created for each model." + source: auth.models.Permission + type: model_class + Group: + description: 'Groups are a generic way of categorizing users to apply permissions, + or + + some other label, to those users. A user can belong to any number of + + groups. + + + A user in a group automatically has all the permissions granted to that + + group. For example, if the group ''Site editors'' has the permission + + can_edit_home_page, any user in that group will have that permission. + + + Beyond permissions, groups are a convenient way to categorize users to + + apply some label, or extended functionality, to them. For example, you + + could create a group ''Special users'', and you could write code that would + + do special things to those users -- such as giving them access to a + + members-only portion of your site, or sending them members-only email + + messages.' + source: auth.models.Group + type: model_class + User: + description: 'Users within the Django authentication system are represented by + this + + model. + + + Username and password are required. Other fields are optional.' + source: auth.models.User + type: model_class + ContentType: + description: ContentType(id, app_label, model) + source: contenttypes.models.ContentType + type: model_class + Session: + description: 'Django provides full support for anonymous sessions. The session + + framework lets you store and retrieve arbitrary data on a + + per-site-visitor basis. It stores data on the server side and + + abstracts the sending and receiving of cookies. Cookies contain a + + session ID -- not the data itself. + + + The Django sessions framework is entirely cookie-based. It does + + not fall back to putting session IDs in URLs. This is an intentional + + design decision. Not only does that behavior make URLs ugly, it makes + + your site vulnerable to session-ID theft via the "Referer" header. + + + For complete documentation on using Sessions in your code, consult + + the sessions documentation that is shipped with Django (also available + + on the Django web site).' + source: sessions.models.Session + type: model_class + DealersMake: + description: 'Represents the relationship between a car dealer and a car make. + + + This model establishes a many-to-many relationship between dealers and + + car makes, allowing each dealer to be associated with multiple car makes + + and each car make to be associated with multiple dealers. It also keeps + + track of the date and time when the relationship was added. + + + :ivar dealer: The dealer associated with the car make. + + :type dealer: ForeignKey + + :ivar car_make: The car make associated with the dealer. + + :type car_make: ForeignKey + + :ivar added_at: The date and time when the relationship was created. + + :type added_at: DateTimeField' + source: inventory.models.DealersMake + type: model_class + VatRate: + description: VatRate(id, rate, is_active, created_at) + source: inventory.models.VatRate + type: model_class + CarMake: + description: CarMake(id_car_make, name, slug, arabic_name, logo, is_sa_import, + car_type) + source: inventory.models.CarMake + type: model_class + CarModel: + description: CarModel(id_car_model, id_car_make, name, arabic_name, slug) + source: inventory.models.CarModel + type: model_class + CarSerie: + description: CarSerie(id_car_serie, id_car_model, name, arabic_name, year_begin, + year_end, generation_name, slug) + source: inventory.models.CarSerie + type: model_class + CarTrim: + description: CarTrim(id_car_trim, id_car_serie, name, arabic_name, start_production_year, + end_production_year, slug) + source: inventory.models.CarTrim + type: model_class + CarEquipment: + description: CarEquipment(id_car_equipment, id_car_trim, name, arabic_name, year_begin, + slug) + source: inventory.models.CarEquipment + type: model_class + CarSpecification: + description: CarSpecification(id_car_specification, name, arabic_name, id_parent, + slug) + source: inventory.models.CarSpecification + type: model_class + CarSpecificationValue: + description: CarSpecificationValue(id_car_specification_value, id_car_trim, id_car_specification, + value, unit) + source: inventory.models.CarSpecificationValue + type: model_class + CarOption: + description: CarOption(id_car_option, name, arabic_name, id_parent, slug) + source: inventory.models.CarOption + type: model_class + CarOptionValue: + description: CarOptionValue(id_car_option_value, id_car_option, id_car_equipment, + value, unit, is_base) + source: inventory.models.CarOptionValue + type: model_class + AdditionalServices: + description: AdditionalServices(id, name, arabic_name, description, price, taxable, + uom, dealer, item) + source: inventory.models.AdditionalServices + type: model_class + Car: + description: Car(id, slug, created_at, updated_at, item_model, vin, dealer, vendor, + id_car_make, id_car_model, year, id_car_serie, id_car_trim, status, stock_type, + remarks, mileage, receiving_date, hash) + source: inventory.models.Car + type: model_class + CarTransfer: + description: CarTransfer(id, car, from_dealer, to_dealer, transfer_date, quantity, + remarks, status, is_approved, active, created_at, updated_at) + source: inventory.models.CarTransfer + type: model_class + CarReservation: + description: CarReservation(id, car, reserved_by, reserved_at, reserved_until) + source: inventory.models.CarReservation + type: model_class + CarFinance: + description: CarFinance(id, car, cost_price, selling_price, discount_amount, is_sold) + source: inventory.models.CarFinance + type: model_class + ExteriorColors: + description: ExteriorColors(id, name, arabic_name, rgb) + source: inventory.models.ExteriorColors + type: model_class + InteriorColors: + description: InteriorColors(id, name, arabic_name, rgb) + source: inventory.models.InteriorColors + type: model_class + CarColors: + description: CarColors(id, car, exterior, interior) + source: inventory.models.CarColors + type: model_class + CustomCard: + description: CustomCard(id, car, custom_number, custom_date) + source: inventory.models.CustomCard + type: model_class + CarLocation: + description: CarLocation(id, car, owner, showroom, description, created_at, updated_at) + source: inventory.models.CarLocation + type: model_class + CarRegistration: + description: CarRegistration(id, car, plate_number, text1, text2, text3, registration_date) + source: inventory.models.CarRegistration + type: model_class + Dealer: + description: Dealer(id, user, crn, vrn, arabic_name, name, phone_number, address, + logo, entity, joined_at, updated_at, slug) + source: inventory.models.Dealer + type: model_class + Staff: + description: Staff(id, staff_member, dealer, name, arabic_name, phone_number, + staff_type, active, created, updated, slug) + source: inventory.models.Staff + type: model_class + Customer: + description: Customer(id, dealer, customer_model, user, title, first_name, last_name, + gender, dob, email, national_id, phone_number, address, active, image, created, + updated, slug) + source: inventory.models.Customer + type: model_class + Organization: + description: Organization(id, dealer, customer_model, user, name, arabic_name, + crn, vrn, email, phone_number, address, logo, active, created, updated, slug) + source: inventory.models.Organization + type: model_class + Representative: + description: Representative(id, dealer, name, arabic_name, id_number, phone_number, + email, address) + source: inventory.models.Representative + type: model_class + Lead: + description: Lead(id, dealer, first_name, last_name, email, phone_number, address, + lead_type, customer, organization, id_car_make, id_car_model, source, channel, + staff, status, next_action, next_action_date, is_converted, converted_at, created, + updated, slug) + source: inventory.models.Lead + type: model_class + Schedule: + description: Schedule(id, name, func, hook, args, kwargs, schedule_type, minutes, + repeats, next_run, cron, task, cluster, intended_date_kwarg) + source: django_q.models.Schedule + type: model_class + LeadStatusHistory: + description: LeadStatusHistory(id, lead, old_status, new_status, changed_by, changed_at) + source: inventory.models.LeadStatusHistory + type: model_class + Opportunity: + description: Opportunity(id, dealer, customer, organization, car, crn, vrn, salary, + priority, stage, staff, lead, probability, amount, expected_revenue, vehicle_of_interest_make, + vehicle_of_interest_model, expected_close_date, created, updated, estimate, + slug, loss_reason) + source: inventory.models.Opportunity + type: model_class + Notes: + description: Notes(id, dealer, content_type, object_id, note, created_by, created, + updated) + source: inventory.models.Notes + type: model_class + Tasks: + description: Tasks(id, dealer, content_type, object_id, title, description, due_date, + completed, assigned_to, created_by, created, updated) + source: inventory.models.Tasks + type: model_class + Email: + description: Email(id, content_type, object_id, from_email, to_email, subject, + message, status, created_by, created, updated) + source: inventory.models.Email + type: model_class + Activity: + description: Activity(id, dealer, content_type, object_id, activity_type, notes, + created_by, created, updated) + source: inventory.models.Activity + type: model_class + Notification: + description: Notification(id, user, message, is_read, created) + source: inventory.models.Notification + type: model_class + Vendor: + description: Vendor(id, dealer, crn, vrn, vendor_model, arabic_name, name, contact_person, + phone_number, email, address, logo, active, created_at, slug) + source: inventory.models.Vendor + type: model_class + Payment: + description: Payment(id, amount, payment_method, reference_number, payment_date) + source: inventory.models.Payment + type: model_class + Refund: + description: Refund(id, payment, amount, reason, refund_date) + source: inventory.models.Refund + type: model_class + UserActivityLog: + description: UserActivityLog(id, user, action, timestamp) + source: inventory.models.UserActivityLog + type: model_class + SaleOrder: + description: SaleOrder(id, estimate, invoice, payment_method, comments, formatted_order_id, + opportunity, customer, car, agreed_price, down_payment_amount, trade_in_value, + trade_in_vehicle, loan_amount, total_paid_amount, remaining_balance, status, + order_date, expected_delivery_date, actual_delivery_date, cancelled_date, cancellation_reason, + created_at, updated_at, created_by, last_modified_by) + source: inventory.models.SaleOrder + type: model_class + CustomGroup: + description: CustomGroup(id, name, dealer, group) + source: inventory.models.CustomGroup + type: model_class + DealerSettings: + description: DealerSettings(id, dealer, invoice_cash_account, invoice_prepaid_account, + invoice_unearned_account, bill_cash_account, bill_prepaid_account, bill_unearned_account, + additional_info) + source: inventory.models.DealerSettings + type: model_class + PaymentHistory: + description: PaymentHistory(id, user, user_data, amount, currency, payment_date, + status, payment_method, transaction_id, invoice_number, order_reference, gateway_response, + gateway_name, description, is_recurring, billing_email, billing_address, created_at, + updated_at) + source: inventory.models.PaymentHistory + type: model_class + CarVIN: + description: CarVIN(id, vin, created) + source: api.models.CarVIN + type: model_class + EmailAddress: + description: EmailAddress(id, user, email, verified, primary) + source: account.models.EmailAddress + type: model_class + EmailConfirmation: + description: EmailConfirmation(id, email_address, created, sent, key) + source: account.models.EmailConfirmation + type: model_class + SocialApp: + description: SocialApp(id, provider, provider_id, name, client_id, secret, key, + settings) + source: socialaccount.models.SocialApp + type: model_class + SocialAccount: + description: SocialAccount(id, user, provider, uid, last_login, date_joined, extra_data) + source: socialaccount.models.SocialAccount + type: model_class + SocialToken: + description: SocialToken(id, app, account, token, token_secret, expires_at) + source: socialaccount.models.SocialToken + type: model_class + Request: + description: Request(id, path, query_params, raw_body, body, method, start_time, + view_name, end_time, time_taken, encoded_headers, meta_time, meta_num_queries, + meta_time_spent_queries, pyprofile, prof_file, num_sql_queries) + source: silk.models.Request + type: model_class + Response: + description: Response(id, request, status_code, raw_body, body, encoded_headers) + source: silk.models.Response + type: model_class + SQLQuery: + description: SQLQuery(id, query, start_time, end_time, time_taken, identifier, + request, traceback, analysis) + source: silk.models.SQLQuery + type: model_class + Profile: + description: Profile(id, name, start_time, end_time, request, time_taken, file_path, + line_num, end_line_num, func_name, exception_raised, dynamic) + source: silk.models.Profile + type: model_class + Token: + description: The default authorization token model. + source: authtoken.models.Token + type: model_class + TokenProxy: + description: Proxy mapping pk to user pk for use in admin. + source: authtoken.models.TokenProxy + type: model_class + BankAccountModel: + description: Base Bank Account Model Implementation + source: django_ledger.models.BankAccountModel + type: model_class + AccountModel: + description: Base Account Model from Account Model Abstract Class + source: django_ledger.models.AccountModel + type: model_class + ChartOfAccountModel: + description: Base ChartOfAccounts Model + source: django_ledger.models.ChartOfAccountModel + type: model_class + CustomerModel: + description: Base Customer Model Implementation + source: django_ledger.models.CustomerModel + type: model_class + UnitOfMeasureModel: + description: Base UnitOfMeasureModel from Abstract. + source: django_ledger.models.UnitOfMeasureModel + type: model_class + ItemTransactionModel: + description: Base ItemTransactionModel from Abstract. + source: django_ledger.models.ItemTransactionModel + type: model_class + ItemModel: + description: Base ItemModel from Abstract. + source: django_ledger.models.ItemModel + type: model_class + LedgerModel: + description: Base LedgerModel from Abstract. + source: django_ledger.models.LedgerModel + type: model_class + EntityUnitModel: + description: Base Model Class for EntityUnitModel + source: django_ledger.models.EntityUnitModel + type: model_class + VendorModel: + description: Base Vendor Model Implementation + source: django_ledger.models.VendorModel + type: model_class + EntityModel: + description: Entity Model Base Class From Abstract + source: django_ledger.models.EntityModel + type: model_class + EntityStateModel: + description: Entity State Model Base Class from Abstract. + source: django_ledger.models.EntityStateModel + type: model_class + EntityManagementModel: + description: EntityManagement Model Base Class From Abstract + source: django_ledger.models.EntityManagementModel + type: model_class + BillModel: + description: Base BillModel from Abstract. + source: django_ledger.models.BillModel + type: model_class + InvoiceModel: + description: Base Invoice Model from Abstract. + source: django_ledger.models.InvoiceModel + type: model_class + TransactionModel: + description: Base Transaction Model From Abstract. + source: django_ledger.models.TransactionModel + type: model_class + JournalEntryModel: + description: Journal Entry Model Base Class From Abstract + source: django_ledger.models.JournalEntryModel + type: model_class + PurchaseOrderModel: + description: Purchase Order Base Model + source: django_ledger.models.PurchaseOrderModel + type: model_class + EstimateModel: + description: Base EstimateModel Class. + source: django_ledger.models.EstimateModel + type: model_class + ClosingEntryModel: + description: ClosingEntryModel(created, updated, markdown_notes, uuid, entity_model, + ledger_model, closing_date, posted) + source: django_ledger.models.ClosingEntryModel + type: model_class + ClosingEntryTransactionModel: + description: Base ClosingEntryModel Class + source: django_ledger.models.ClosingEntryTransactionModel + type: model_class + ImportJobModel: + description: Transaction Import Job Model Base Class. + source: django_ledger.models.ImportJobModel + type: model_class + StagedTransactionModel: + description: Staged Transaction Model Base Class. + source: django_ledger.models.StagedTransactionModel + type: model_class + ChatLog: + description: 'Handles storage and representation of chat logs between users and + chatbot. + + + This class is designed to store chat logs, which include user messages, chatbot + + responses, their associated dealer, and timestamps of when the chat entries + are + + created. It supports linking chat logs to a specific dealer using a foreign + key + + relationship. The class provides an easy and structured way to manage and retrieve + + historical chat data. + + + :ivar dealer: The dealer associated with this chat log. + + :type dealer: Dealer + + :ivar user_message: The message sent by the user. + + :type user_message: str + + :ivar chatbot_response: The response generated by the chatbot. + + :type chatbot_response: str + + :ivar timestamp: The date and time when the chat log entry was created. + + :type timestamp: datetime' + source: haikalbot.models.ChatLog + type: model_class + AnalysisCache: + description: 'Model to cache analysis results for performance optimization. + + + This model stores cached results of model analysis operations to improve + + performance for repeated queries. It includes a hash of the prompt, user + + information, dealer ID, timestamps, and the cached result in JSON format. + + + :ivar prompt_hash: MD5 hash of the prompt + dealer_id + language + + :type prompt_hash: str + + :ivar user: The user who made the request (optional) + + :type user: User + + :ivar dealer_id: ID of the dealer associated with this cache entry + + :type dealer_id: int + + :ivar created_at: When the cache entry was created + + :type created_at: datetime + + :ivar updated_at: When the cache entry was last updated + + :type updated_at: datetime + + :ivar expires_at: When the cache entry expires + + :type expires_at: datetime + + :ivar result: The cached analysis result + + :type result: dict' + source: haikalbot.models.AnalysisCache + type: model_class + Service: + description: 'Represents a service provided by the appointment system. + + + Author: Adams Pierre David + + Version: 1.1.0 + + Since: 1.0.0' + source: appointment.models.Service + type: model_class + StaffMember: + description: StaffMember(id, user, slot_duration, lead_time, finish_time, appointment_buffer_time, + work_on_saturday, work_on_sunday, created_at, updated_at) + source: appointment.models.StaffMember + type: model_class + AppointmentRequest: + description: 'Represents an appointment request made by a client. + + + Author: Adams Pierre David + + Since: 1.0.0' + source: appointment.models.AppointmentRequest + type: model_class + AppointmentRescheduleHistory: + description: AppointmentRescheduleHistory(id, appointment_request, date, start_time, + end_time, staff_member, reason_for_rescheduling, reschedule_status, id_request, + created_at, updated_at) + source: appointment.models.AppointmentRescheduleHistory + type: model_class + Appointment: + description: 'Represents an appointment made by a client. It is created when the + client confirms the appointment request. + + + Author: Adams Pierre David + + Version: 1.1.0 + + Since: 1.0.0' + source: appointment.models.Appointment + type: model_class + Config: + description: 'Represents configuration settings for the appointment system. There + can only be one Config object in the database. + + If you want to change the settings, you must edit the existing Config object. + + + Author: Adams Pierre David + + Version: 1.1.0 + + Since: 1.1.0' + source: appointment.models.Config + type: model_class + PaymentInfo: + description: 'Represents payment information for an appointment. + + + Author: Adams Pierre David + + Version: 1.1.0 + + Since: 1.0.0' + source: appointment.models.PaymentInfo + type: model_class + EmailVerificationCode: + description: 'Represents an email verification code for a user when the email + already exists in the database. + + + Author: Adams Pierre David + + Version: 1.1.0 + + Since: 1.1.0' + source: appointment.models.EmailVerificationCode + type: model_class + PasswordResetToken: + description: 'Represents a password reset token for users. + + + Author: Adams Pierre David + + Version: 3.x.x + + Since: 3.x.x' + source: appointment.models.PasswordResetToken + type: model_class + DayOff: + description: DayOff(id, staff_member, start_date, end_date, description, created_at, + updated_at) + source: appointment.models.DayOff + type: model_class + WorkingHours: + description: WorkingHours(id, staff_member, day_of_week, start_time, end_time, + created_at, updated_at) + source: appointment.models.WorkingHours + type: model_class + Task: + description: Task(id, task_name, task_params, task_hash, verbose_name, priority, + run_at, repeat, repeat_until, queue, attempts, failed_at, last_error, locked_by, + locked_at, creator_content_type, creator_object_id) + source: background_task.models.Task + type: model_class + Success: + description: Success(id, name, func, hook, args, kwargs, result, group, cluster, + started, stopped, success, attempt_count) + source: django_q.models.Success + type: model_class + Failure: + description: Failure(id, name, func, hook, args, kwargs, result, group, cluster, + started, stopped, success, attempt_count) + source: django_q.models.Failure + type: model_class + OrmQ: + description: OrmQ(id, key, payload, lock) + source: django_q.models.OrmQ + type: model_class + Plan: + description: Plan(id, order, created, updated_at, name, description, default, + available, visible, customized, url) + source: plans.models.Plan + type: model_class + BillingInfo: + description: BillingInfo(id, created, updated_at, user, tax_number, name, street, + zipcode, city, country, shipping_name, shipping_street, shipping_zipcode, shipping_city) + source: plans.models.BillingInfo + type: model_class + UserPlan: + description: UserPlan(id, created, updated_at, user, plan, expire, active) + source: plans.models.UserPlan + type: model_class + Pricing: + description: Pricing(id, created, updated_at, name, period, url) + source: plans.models.Pricing + type: model_class + PlanPricing: + description: PlanPricing(id, created, updated_at, plan, pricing, price, order, + has_automatic_renewal, visible) + source: plans.models.PlanPricing + type: model_class + Quota: + description: Quota(id, order, created, updated_at, codename, name, unit, description, + is_boolean, url) + source: plans.models.Quota + type: model_class + PlanQuota: + description: PlanQuota(id, created, updated_at, plan, quota, value) + source: plans.models.PlanQuota + type: model_class + Order: + description: Order(id, created, updated_at, user, flat_name, plan, pricing, completed, + plan_extended_from, plan_extended_until, amount, tax, currency, status) + source: plans.models.Order + type: model_class + Invoice: + description: Invoice(id, created, updated_at, user, order, number, full_number, + type, issued, issued_duplicate, selling_date, payment_date, unit_price_net, + quantity, total_net, total, tax_total, tax, rebate, currency, item_description, + buyer_name, buyer_street, buyer_zipcode, buyer_city, buyer_country, buyer_tax_number, + shipping_name, shipping_street, shipping_zipcode, shipping_city, shipping_country, + require_shipment, issuer_name, issuer_street, issuer_zipcode, issuer_city, issuer_country, + issuer_tax_number) + source: plans.models.Invoice + type: model_class + RecurringUserPlan: + description: RecurringUserPlan(id, created, updated_at, user_plan, token, payment_provider, + pricing, amount, tax, currency, renewal_triggered_by, _has_automatic_renewal_backup_deprecated, + token_verified, card_expire_year, card_expire_month, card_masked_number) + source: plans.models.RecurringUserPlan + type: model_class + Sequence: + description: Sequence(name, last) + source: sequences.models.Sequence + type: model_class + ExportPDFSettings: + description: ExportPDFSettings(id, created, modified, title, active, page_size, + items_per_page, page_margin_mm, font_name, header_font_size, body_font_size, + logo, header_background_color, grid_line_color, grid_line_width, show_header, + show_logo, show_export_time, show_page_numbers, rtl_support, content_alignment, + header_alignment, title_alignment, table_spacing, max_chars_per_line) + source: django_pdf_actions.models.ExportPDFSettings + type: model_class + CompletedTask: + description: CompletedTask(id, task_name, task_params, task_hash, verbose_name, + priority, run_at, repeat, repeat_until, queue, attempts, failed_at, last_error, + locked_by, locked_at, creator_content_type, creator_object_id) + source: background_task.models.CompletedTask + type: model_class +glossary: {} diff --git a/haikalbot/management/commands/generate_support_yaml.py b/haikalbot/management/commands/generate_support_yaml.py index 37cc89e0..cfdee27e 100644 --- a/haikalbot/management/commands/generate_support_yaml.py +++ b/haikalbot/management/commands/generate_support_yaml.py @@ -5,9 +5,11 @@ import importlib import yaml import os from django.conf import settings +from django.template.loaders.app_directories import get_app_template_dirs + class Command(BaseCommand): - help = "Generate YAML support knowledge base from Django views and models" + help = "Generate YAML support knowledge base from Django views, models, and templates" def handle(self, *args, **kwargs): output_file = "haikal_kb.yaml" @@ -18,6 +20,8 @@ class Command(BaseCommand): "generated_from": "Django", }, "features": {}, + "user_workflows": {}, # New section for step-by-step instructions + "templates": {}, "glossary": {} } @@ -41,6 +45,49 @@ class Command(BaseCommand): all_models.append((model._meta.app_label, model.__name__, extract_doc(model))) return all_models + def get_all_templates(): + template_dirs = get_app_template_dirs('templates') + templates = [] + + for template_dir in template_dirs: + app_name = os.path.basename(os.path.dirname(os.path.dirname(template_dir))) + for root, dirs, files in os.walk(template_dir): + for file in files: + if file.endswith(('.html', '.htm', '.txt')): + rel_path = os.path.relpath(os.path.join(root, file), template_dir) + with open(os.path.join(root, file), 'r', encoding='utf-8', errors='ignore') as f: + try: + content = f.read() + # Extract template comment documentation if it exists + doc = "" + if '{# DOC:' in content and '#}' in content: + doc_parts = content.split('{# DOC:') + for part in doc_parts[1:]: + if '#}' in part: + doc += part.split('#}')[0].strip() + "\n" + except Exception as e: + self.stdout.write(self.style.WARNING(f"Error reading {rel_path}: {e}")) + continue + + templates.append((app_name, rel_path, doc.strip())) + return templates + + # Look for workflow documentation files + def get_workflow_docs(): + workflows = {} + workflow_dir = os.path.join(settings.BASE_DIR, 'docs', 'workflows') + if os.path.exists(workflow_dir): + for file in os.listdir(workflow_dir): + if file.endswith('.yaml') or file.endswith('.yml'): + try: + with open(os.path.join(workflow_dir, file), 'r') as f: + workflow_data = yaml.safe_load(f) + for workflow_name, workflow_info in workflow_data.items(): + workflows[workflow_name] = workflow_info + except Exception as e: + self.stdout.write(self.style.WARNING(f"Error reading workflow file {file}: {e}")) + return workflows + # Extract views for app, mod in get_all_views_modules(): for name, obj in inspect.getmembers(mod, inspect.isfunction): @@ -52,6 +99,25 @@ class Command(BaseCommand): "type": "view_function" } + # Look for @workflow decorator or WORKFLOW tag in docstring + if hasattr(obj, 'workflow_steps') or 'WORKFLOW:' in doc: + workflow_name = name.replace('_', ' ').title() + steps = [] + + if hasattr(obj, 'workflow_steps'): + steps = obj.workflow_steps + elif 'WORKFLOW:' in doc: + workflow_section = doc.split('WORKFLOW:')[1].strip() + steps_text = workflow_section.split('\n') + steps = [step.strip() for step in steps_text if step.strip()] + + if steps: + kb["user_workflows"][workflow_name] = { + "description": f"How to {name.replace('_', ' ')}", + "steps": steps, + "source": f"{app}.views.{name}" + } + # Extract models for app, name, doc in get_all_model_classes(): if doc: @@ -61,6 +127,52 @@ class Command(BaseCommand): "type": "model_class" } + # Extract templates + for app, template_path, doc in get_all_templates(): + template_id = f"{app}:{template_path}" + if doc: # Only include templates with documentation + kb["templates"][template_id] = { + "description": doc, + "path": template_path, + "app": app + } + + # Add workflow documentation + kb["user_workflows"].update(get_workflow_docs()) + + # Add manual workflow examples if no workflows were found + if not kb["user_workflows"]: + kb["user_workflows"] = { + "Add New Car": { + "description": "How to add a new car to the inventory", + "steps": [ + "Navigate to the Inventory section by clicking 'Inventory' in the main menu", + "Click the 'Add Car' button in the top right corner", + "Enter the VIN number or scan it using the barcode scanner", + "Select the car make from the dropdown menu", + "Select the car series from the available options", + "Select the trim level for the car", + "Fill in additional details like color, mileage, and price", + "Click 'Save' to add the car to inventory, or 'Save & Add Another' to continue adding cars" + ], + "source": "manual_documentation" + }, + "Create New Invoice": { + "description": "How to create a new invoice", + "steps": [ + "Navigate to the Finance section by clicking 'Finance' in the main menu", + "Click the 'Invoices' tab", + "Click the 'Create New Invoice' button", + "Select a customer from the dropdown or click 'Add New Customer'", + "Select the car(s) to include in the invoice", + "Add any additional services or parts by clicking 'Add Item'", + "Set the payment terms and due date", + "Click 'Save Draft' to save without finalizing, or 'Finalize Invoice' to complete" + ], + "source": "manual_documentation" + } + } + with open(output_file, "w", encoding="utf-8") as f: yaml.dump(kb, f, allow_unicode=True, sort_keys=False) diff --git a/haikalbot/migrations/0001_initial.py b/haikalbot/migrations/0001_initial.py new file mode 100644 index 00000000..944b6da5 --- /dev/null +++ b/haikalbot/migrations/0001_initial.py @@ -0,0 +1,50 @@ +# Generated by Django 5.2.1 on 2025-06-12 16:25 + +import django.db.models.deletion +import django.utils.timezone +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('inventory', '__first__'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='AnalysisCache', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('prompt_hash', models.CharField(db_index=True, max_length=64)), + ('dealer_id', models.IntegerField(blank=True, db_index=True, null=True)), + ('created_at', models.DateTimeField(default=django.utils.timezone.now)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('expires_at', models.DateTimeField(db_index=True)), + ('result', models.JSONField()), + ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name_plural': 'Analysis caches', + 'indexes': [models.Index(fields=['prompt_hash', 'dealer_id'], name='haikalbot_a_prompt__b98e1e_idx'), models.Index(fields=['expires_at'], name='haikalbot_a_expires_e790cd_idx')], + }, + ), + migrations.CreateModel( + name='ChatLog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('user_message', models.TextField()), + ('chatbot_response', models.TextField()), + ('timestamp', models.DateTimeField(auto_now_add=True, db_index=True)), + ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='chatlogs', to='inventory.dealer')), + ], + options={ + 'ordering': ['-timestamp'], + 'indexes': [models.Index(fields=['dealer', 'timestamp'], name='haikalbot_c_dealer__6f8d63_idx')], + }, + ), + ] diff --git a/haikalbot/model_analyzer_refactored.zip b/haikalbot/model_analyzer_refactored.zip deleted file mode 100644 index 46ea217c..00000000 Binary files a/haikalbot/model_analyzer_refactored.zip and /dev/null differ diff --git a/haikalbot/run_haikal_qa.py b/haikalbot/run_haikal_qa.py deleted file mode 100644 index cd026642..00000000 --- a/haikalbot/run_haikal_qa.py +++ /dev/null @@ -1,19 +0,0 @@ -from langchain.document_loaders import TextLoader -from langchain.indexes import VectorstoreIndexCreator -from langchain.chat_models import ChatOpenAI -from langchain.chains import RetrievalQA - -# Load YAML doc -loader = TextLoader("haikal_kb.yaml") -index = VectorstoreIndexCreator().from_loaders([loader]) - -# Setup QA chain -qa = RetrievalQA.from_chain_type( - llm=ChatOpenAI(model="gpt-3.5-turbo", temperature=0), - retriever=index.vectorstore.as_retriever() -) - -# Ask a question -query = "How do I add a new invoice?" -response = qa.run(query) -print("Answer:", response) \ No newline at end of file diff --git a/haikalbot/utils/ask_haikalbot.py b/haikalbot/utils/ask_haikalbot.py new file mode 100644 index 00000000..bcf11aea --- /dev/null +++ b/haikalbot/utils/ask_haikalbot.py @@ -0,0 +1,77 @@ +from langchain_community.document_loaders import TextLoader +from langchain.indexes import VectorstoreIndexCreator +from langchain_community.llms import Ollama +from langchain.chains import RetrievalQA +from langchain_community.embeddings import HuggingFaceEmbeddings +from langchain.prompts import PromptTemplate +# from django.conf import settings + + +# Load YAML doc +loader = TextLoader("haikal_kb.yaml") + +# Create embeddings model +embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2") + +# Create an instance of VectorstoreIndexCreator with the embeddings +index_creator = VectorstoreIndexCreator(embedding=embeddings) + +# Then call the from_loaders method on the instance +index = index_creator.from_loaders([loader]) + +# Create LLM instance +llm = Ollama(model="qwen3:8b", temperature=0.3) + +# Define a custom prompt template for instructional responses +template = """ +You are Haikal, an assistant for the car inventory management system. +Your goal is to provide clear step-by-step instructions for users to complete tasks. + +Use the following pieces of context to answer the question at the end. +If you don't know the answer, just say you don't know. Don't try to make up an answer. + +Context: +{context} + +Question: {question} + +Provide a clear step-by-step guide with numbered instructions. Include: +1. Where to click in the interface +2. What to enter or select +3. Any buttons to press to complete the action +4. Any alternatives or shortcuts if available + +Helpful Step-by-Step Instructions:""" + +PROMPT = PromptTemplate( + template=template, + input_variables=["context", "question"] +) + +# Setup QA chain +qa = RetrievalQA.from_chain_type( + llm=llm, + chain_type="stuff", + retriever=index.vectorstore.as_retriever(), + return_source_documents=True, + chain_type_kwargs={"prompt": PROMPT} +) + +# Function to run a query +def ask_haikal(query): + response = qa.invoke({"query": query}) + print("\n" + "="*50) + print(f"Question: {query}") + print("="*50) + print("\nAnswer:") + print(response["result"]) + print("\nSources:") + for doc in response["source_documents"]: + print(f"- {doc.metadata.get('source', 'Unknown source')}") + print("="*50) + return response["result"] + +# # Example query +# if __name__ == "__main__": +# query = "How do I add a new car to the inventory? answer in Arabic" +# ask_haikal(query) diff --git a/haikalbot/views.py b/haikalbot/views.py index 2bebda15..9467391d 100644 --- a/haikalbot/views.py +++ b/haikalbot/views.py @@ -4,12 +4,12 @@ from django.shortcuts import render from django.utils.translation import gettext as _ from django.views import View import logging - from .ai_agent import analyze_prompt +# from .haikal_agent import DatabaseInsightSystem, analyze_prompt_sync from .utils.export import export_to_excel, export_to_csv logger = logging.getLogger(__name__) - +# analyze_prompt_ai = DatabaseInsightSystem class HaikalBot(LoginRequiredMixin, View): def get(self, request, *args, **kwargs): @@ -33,11 +33,9 @@ class HaikalBot(LoginRequiredMixin, View): if not prompt: error_msg = _("Prompt is required.") if language != "ar" else "الاستعلام مطلوب." return JsonResponse({"status": "error", "error": error_msg}, status=400) - try: result = analyze_prompt(prompt) - # Handle export requests if data is available if export and result.get("status") == "success" and result.get("data"): try: if export == "excel": diff --git a/requirements.txt b/requirements.txt index e39311be..958851d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,8 +6,10 @@ alabaster==1.0.0 albucore==0.0.24 albumentations==2.0.7 annotated-types==0.7.0 +anthropic==0.52.2 anyio==4.9.0 arabic-reshaper==3.0.0 +argcomplete==3.6.2 arrow==1.3.0 asgiref==3.8.1 astor==0.8.1 @@ -19,13 +21,17 @@ beautifulsoup4==4.13.4 bleach==6.2.0 blessed==1.21.0 blinker==1.9.0 +boto3==1.38.29 +botocore==1.38.29 Brotli==1.1.0 +cachetools==5.5.2 cattrs==24.1.3 certifi==2025.4.26 cffi==1.17.1 chardet==5.2.0 charset-normalizer==3.4.2 click==8.2.1 +cohere==5.15.0 colorama==0.4.6 commonmark==0.9.1 contourpy==1.3.2 @@ -55,6 +61,7 @@ django-cors-headers==4.7.0 django-countries==7.6.1 django-crispy-forms==2.4 django-debug-toolbar==5.2.0 +django-easy-audit==1.3.7 django-extensions==4.1 django-filter==25.1 django-formtools==2.5.1 @@ -94,7 +101,11 @@ docutils==0.21.2 easy-thumbnails==2.10 emoji==2.14.1 et_xmlfile==2.0.0 +eval_type_backport==0.2.2 +executing==2.2.0 Faker==37.3.0 +fasta2a==0.2.14 +fastavro==1.11.1 filelock==3.18.0 fire==0.7.0 fonttools==4.58.0 @@ -102,28 +113,37 @@ fpdf==1.7.2 fpdf2==2.8.3 frozenlist==1.6.0 fsspec==2025.5.1 +google-auth==2.40.2 +google-genai==1.18.0 +googleapis-common-protos==1.70.0 gprof2dot==2025.4.14 graphqlclient==0.2.4 greenlet==3.2.2 +griffe==1.7.3 +groq==0.26.0 h11==0.16.0 h2==4.2.0 +hf-xet==1.1.3 hpack==4.1.0 hstspreload==2025.1.1 httpcore==1.0.9 httpx==0.28.1 httpx-sse==0.4.0 +huggingface-hub==0.32.4 hyperframe==6.1.0 icalendar==6.3.1 idna==3.10 imageio==2.37.0 imagesize==1.4.1 imgaug==0.4.0 +importlib_metadata==8.7.0 iso4217==1.12.20240625 isodate==0.7.2 isort==6.0.1 itsdangerous==2.2.0 Jinja2==3.1.6 jiter==0.10.0 +jmespath==1.0.1 joblib==1.5.1 jsonpatch==1.33 jsonpointer==3.0.0 @@ -132,12 +152,15 @@ kiwisolver==1.4.8 langchain==0.3.25 langchain-community==0.3.24 langchain-core==0.3.61 +langchain-ollama==0.3.3 langchain-text-splitters==0.3.8 langsmith==0.3.42 lazy_loader==0.4 ledger==1.0.1 libretranslatepy==2.1.4 lmdb==1.6.2 +logfire==3.18.0 +logfire-api==3.17.0 luhnchecker==0.0.12 lxml==5.4.0 Markdown==3.8 @@ -146,7 +169,9 @@ MarkupSafe==3.0.2 marshmallow==3.26.1 matplotlib==3.10.3 mccabe==0.7.0 +mcp==1.9.2 mdurl==0.1.2 +mistralai==1.8.1 MouseInfo==0.1.3 mpmath==1.3.0 multidict==6.4.4 @@ -158,11 +183,19 @@ num2words==0.5.14 numpy==2.2.6 oauthlib==3.2.2 ofxtools==0.9.5 +ollama==0.4.8 openai==1.82.0 opencv-contrib-python==4.11.0.86 opencv-python==4.11.0.86 opencv-python-headless==4.11.0.86 openpyxl==3.1.5 +opentelemetry-api==1.34.0 +opentelemetry-exporter-otlp-proto-common==1.34.0 +opentelemetry-exporter-otlp-proto-http==1.34.0 +opentelemetry-instrumentation==0.55b0 +opentelemetry-proto==1.34.0 +opentelemetry-sdk==1.34.0 +opentelemetry-semantic-conventions==0.55b0 opt_einsum==3.4.0 orjson==3.10.18 outcome==1.3.0.post0 @@ -174,18 +207,25 @@ phonenumbers==8.13.42 pillow==10.4.0 platformdirs==4.3.8 prometheus_client==0.22.0 +prompt_toolkit==3.0.51 propcache==0.3.1 -protobuf==6.31.0 +protobuf==5.29.5 psycopg==3.2.9 psycopg-binary==3.2.9 psycopg-c==3.2.9 psycopg2-binary==2.9.10 py-moneyed==3.0 +pyasn1==0.6.1 +pyasn1_modules==0.4.2 PyAutoGUI==0.9.54 pyclipper==1.3.0.post6 pycodestyle==2.13.0 pycparser==2.22 pydantic==2.11.5 +pydantic-ai==0.2.14 +pydantic-ai-slim==0.2.14 +pydantic-evals==0.2.14 +pydantic-graph==0.2.14 pydantic-settings==2.9.1 pydantic_core==2.33.2 pydotplus==2.0.2 @@ -212,6 +252,7 @@ python-bidi==0.6.6 python-dateutil==2.9.0.post0 python-docx==1.1.2 python-dotenv==1.1.0 +python-multipart==0.0.20 python-openid==2.2.5 python-slugify==8.0.4 python-stdnum==2.1 @@ -234,12 +275,16 @@ requests-oauthlib==2.0.0 requests-toolbelt==1.0.0 rfc3986==2.0.0 rich==14.0.0 +rsa==4.9.1 rubicon-objc==0.5.0 +s3transfer==0.13.0 sacremoses==0.1.1 +safetensors==0.5.3 scikit-image==0.25.2 scikit-learn==1.6.1 scipy==1.15.3 selenium==4.33.0 +sentence-transformers==4.1.0 sentencepiece==0.2.0 shapely==2.1.1 simsimd==6.2.1 @@ -251,7 +296,9 @@ sortedcontainers==2.4.0 soupsieve==2.7 SQLAlchemy==2.0.41 sqlparse==0.5.3 +sse-starlette==2.3.6 stanza==1.10.1 +starlette==0.47.0 stringzilla==3.12.5 suds==1.2.0 swapper==1.3.0 @@ -264,14 +311,17 @@ threadpoolctl==3.6.0 tifffile==2025.5.24 tinycss2==1.4.0 tinyhtml5==2.0.0 +tokenizers==0.21.1 tomli==2.2.1 tomlkit==0.13.2 torch==2.7.0 tqdm==4.67.1 +transformers==4.52.4 trio==0.30.0 trio-websocket==0.12.2 twilio==9.6.1 types-python-dateutil==2.9.0.20250516 +types-requests==2.32.0.20250602 typing-inspect==0.9.0 typing-inspection==0.4.1 typing_extensions==4.13.2 @@ -279,6 +329,7 @@ tzdata==2025.2 Unidecode==1.4.0 upgrade-requirements==1.7.0 urllib3==2.4.0 +uvicorn==0.34.3 vin==0.6.2 vininfo==1.8.0 vishap==0.1.5 @@ -287,10 +338,13 @@ wcwidth==0.2.13 weasyprint==65.1 webencodings==0.5.1 websocket-client==1.8.0 +websockets==15.0.1 Werkzeug==3.1.3 wikipedia==1.4.0 +wrapt==1.17.2 wsproto==1.2.0 xmlsec==1.3.15 yarl==1.20.0 +zipp==3.22.0 zopfli==0.2.3.post1 zstandard==0.23.0 diff --git a/run_haikal_qa.py b/run_haikal_qa.py new file mode 100644 index 00000000..83b11ff7 --- /dev/null +++ b/run_haikal_qa.py @@ -0,0 +1,77 @@ +from langchain_community.document_loaders import TextLoader +from langchain.indexes import VectorstoreIndexCreator +from langchain_community.llms import Ollama +from langchain.chains import RetrievalQA +from langchain_community.embeddings import HuggingFaceEmbeddings +from langchain.prompts import PromptTemplate +# from django.conf import settings + + +# Load YAML doc +loader = TextLoader("haikal_kb.yaml") + +# Create embeddings model +embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2") + +# Create an instance of VectorstoreIndexCreator with the embeddings +index_creator = VectorstoreIndexCreator(embedding=embeddings) + +# Then call the from_loaders method on the instance +index = index_creator.from_loaders([loader]) + +# Create LLM instance +llm = Ollama(model="qwen3:8b", temperature=0.3) + +# Define a custom prompt template for instructional responses +template = """ +You are Haikal, an assistant for the car inventory management system. +Your goal is to provide clear step-by-step instructions for users to complete tasks. + +Use the following pieces of context to answer the question at the end. +If you don't know the answer, just say you don't know. Don't try to make up an answer. + +Context: +{context} + +Question: {question} + +Provide a clear step-by-step guide with numbered instructions. Include: +1. Where to click in the interface +2. What to enter or select +3. Any buttons to press to complete the action +4. Any alternatives or shortcuts if available + +Helpful Step-by-Step Instructions:""" + +PROMPT = PromptTemplate( + template=template, + input_variables=["context", "question"] +) + +# Setup QA chain +qa = RetrievalQA.from_chain_type( + llm=llm, + chain_type="stuff", + retriever=index.vectorstore.as_retriever(), + return_source_documents=True, + chain_type_kwargs={"prompt": PROMPT} +) + +# Function to run a query +def ask_haikal(query): + response = qa.invoke({"query": query}) + print("\n" + "="*50) + print(f"Question: {query}") + print("="*50) + print("\nAnswer:") + print(response["result"]) + print("\nSources:") + for doc in response["source_documents"]: + print(f"- {doc.metadata.get('source', 'Unknown source')}") + print("="*50) + return response["result"] + +# Example query +if __name__ == "__main__": + query = "How do I add a new car to the inventory? answer in Arabic" + ask_haikal(query) diff --git a/sql_agent.py b/sql_agent.py new file mode 100644 index 00000000..bb38f081 --- /dev/null +++ b/sql_agent.py @@ -0,0 +1,159 @@ +import asyncio +import sqlite3 +import json +from typing import List, Dict + +from pydantic import BaseModel, Field + +from pydantic_ai import Agent, RunContext +from pydantic_ai.models.openai import OpenAIModel +from pydantic_ai.providers.openai import OpenAIProvider +import os + +import logfire + +logfire.configure(send_to_logfire='if-token-present') +logfire.instrument_pydantic_ai() + +# Define the OpenAI model (replace with your actual model if needed) +model = OpenAIModel( + model_name="qwen2.5:14b", # Or your preferred model + provider=OpenAIProvider(base_url='http://localhost:11434/v1') # Or your provider +) + + +class DatabaseSchema(BaseModel): + tables: Dict[str, List[Dict[str, str]]] = Field( + description="A dictionary where keys are table names and values are lists of column dictionaries (name, type)") + + +# Agent to get the database schema +schema_agent = Agent( + model, + deps_type=str, + output_type=str, + system_prompt="""You are a helpful assistant that extracts the schema of a SQLite database. + When the user provides a database path, use the get_database_schema to retrieve the schema. + Your ONLY response should be the raw JSON string representing the database schema. Do not include any other text. + The JSON should be a dictionary where keys are table names, and values are lists of column dictionaries. + Each column dictionary should include 'name', 'type', 'notnull', 'dflt_value', and 'pk' keys. + If there is an error, return a JSON string containing an "error" key with a list of error messages.""" +) + + +@schema_agent.tool +async def get_database_schema(ctx: RunContext[str], db_path: str) -> str: + """Retrieves the schema of the SQLite database and returns it as a JSON string.""" + print(f"Database path: {db_path}") + try: + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + cursor.execute("SELECT name FROM sqlite_master WHERE type='table';") + tables = [row[0] for row in cursor.fetchall()] + print(tables) + + schema = {} + for table in tables: + cursor.execute(f"PRAGMA table_info({table})") + columns = [ + { + "name": col[1], + "type": col[2], + "notnull": col[3], + "dflt_value": col[4], + "pk": col[5], + } + for col in cursor.fetchall() + ] + schema[table] = columns + print(schema) + conn.close() + return json.dumps(schema) + except sqlite3.Error as e: + error_json = json.dumps({"error": [str(e)]}) + return error_json + except Exception as e: + error_json = json.dumps({"error": [str(e)]}) + return error_json + + +# Agent to generate and execute SQL queries +sql_agent = Agent( + model, + deps_type=DatabaseSchema, + output_type=str, + system_prompt="""You are a highly precise SQL query generator for a SQLite database. +You are given the EXACT database schema, which is a dictionary where keys are table names and values are lists of column dictionaries (with 'name' and 'type'). +Your ABSOLUTE priority is to generate SQL queries that ONLY use the table and column names exactly as they appear in this schema to answer the user's question. + +Follow these strict steps: +1. **Analyze User Question:** Understand the user's request. +2. **Match Schema EXACTLY:** Identify the specific table(s) and column(s) in the provided schema whose names EXACTLY match the entities and information requested in the user's question. +3. **Generate STRICT SQL:** Construct a valid SQL query that selects the identified column(s) from the identified table(s). You MUST use the exact names from the schema. Do not use aliases or make any assumptions about naming conventions. Aim for the simplest possible query. +4. **Execute Query:** Use the execute_sql_query to run your generated SQL. +5. **Return interactive Answer as if you are a sports person:** Provide a direct and simple answer to the user's question based on the query results. +6. **No Results:** If the query returns empty list, respond with: 'No matching entries found.' +7. **Error Handling:** If there's any error in generating or executing the SQL, return a JSON string with an "error" key and a list of error messages. + + +""" +) +# Example: +# Schema: {'Country': [{'name': 'id', 'type': 'INTEGER'}, {'name': 'name', 'type': 'TEXT'}]} +# User Question: "What are the country names?" +# Generated SQL: SELECT name FROM Country; +# Expected Answer: The countries are Belgium, England, France, ... + +@sql_agent.tool +async def execute_sql_query(ctx: RunContext[DatabaseSchema], query: str) -> str: + """Executes the SQL query and returns a simple string answer.""" + db_path = os.path.join(os.getcwd(), 'db.sqlite3') + print(query) + try: + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + cursor.execute(query) + results = cursor.fetchall() + columns = [description[0] for description in cursor.description] + rows = [dict(zip(columns, row)) for row in results] + conn.close() + print(rows) + return rows + except Exception as e: + print(e) + + +async def main(): + db_path = os.path.join(os.getcwd(), 'db.sqlite3') + print(f"Database path: {db_path}") + user_question = "how many cars do we have in the inventory" + + # 1. Get the database schema + schema_result = await schema_agent.run(db_path) + print("Schema Agent Response:", schema_result) + print("Schema Agent Output:", schema_result.output) + + if "error" in schema_result.output: + print(f"Error getting schema: {schema_result.output}") + return + + try: + schema_data = json.loads(schema_result.output) + database_schema = DatabaseSchema(tables=schema_data) + print("Parsed Database Schema:", database_schema) + + # 2. Use the schema to answer the user question + sql_response = await sql_agent.run(user_question, database_schema=database_schema.tables) + print("SQL Agent Response:", sql_response) + print("SQL Agent Output:", sql_response.output) + + if "error" in sql_response.output: + print(f"Error executing SQL: {sql_response.output}") + + except json.JSONDecodeError: + print(f"Error: Could not parse schema agent response as JSON: {schema_result.output}") + + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/static/js/tours/add-new-car_tour.json b/static/js/tours/add-new-car_tour.json new file mode 100644 index 00000000..a319e178 --- /dev/null +++ b/static/js/tours/add-new-car_tour.json @@ -0,0 +1,61 @@ +{ + "name": "Add New Car", + "description": "How to add a new car to the inventory", + "steps": [ + { + "title": "Step 1", + "intro": "Navigate to the Inventory section by clicking 'Inventory' in the main menu", + "position": "bottom", + "element": "#inventory-nav", + "click": "#inventory-nav" + }, + { + "title": "Step 2", + "intro": "Click the 'Inventory' button in the top right corner", + "position": "bottom", + "element": ".parent-wrapper label-1" + }, + { + "title": "Step 3", + "intro": "Click the 'Add Car' button in the top right corner", + "position": "bottom", + "element": "#btn-add-car" + }, + { + "title": "Step 4", + "intro": "Enter the VIN number or scan it using the barcode scanner", + "position": "bottom", + "element": "#nv-inventory" + }, + { + "title": "Step 5", + "intro": "Select the car make from the dropdown menu", + "position": "bottom", + "element": "#make-select, select[name='make'], .make-field" + }, + { + "title": "Step 6", + "intro": "Select the car series from the available options", + "position": "bottom", + "element": "#series-select, select[name='series'], .series-field" + }, + { + "title": "Step 7", + "intro": "Select the trim level for the car", + "position": "bottom", + "element": "#trim-select, select[name='trim'], .trim-field" + }, + { + "title": "Step 8", + "intro": "Fill in additional details like color, mileage, and price", + "position": "bottom", + "element": "#price-input, input[name='price'], .price-field" + }, + { + "title": "Step 9", + "intro": "Click 'Save' to add the car to inventory, or 'Save & Add Another' to continue adding cars", + "position": "bottom", + "element": "#inventory-menu, .inventory-nav, nav .inventory" + } + ] +} \ No newline at end of file diff --git a/static/js/tours/create-new-invoice_tour.json b/static/js/tours/create-new-invoice_tour.json new file mode 100644 index 00000000..98862178 --- /dev/null +++ b/static/js/tours/create-new-invoice_tour.json @@ -0,0 +1,52 @@ +{ + "name": "Create New Invoice", + "description": "How to create a new invoice", + "steps": [ + { + "title": "Step 1", + "intro": "Navigate to the Finance section by clicking 'Finance' in the main menu", + "position": "bottom", + "element": "#finance-menu, .finance-nav, nav .finance" + }, + { + "title": "Step 2", + "intro": "Click the 'Invoices' tab", + "position": "bottom", + "element": "#invoice-section, .invoice-tab, #create-invoice" + }, + { + "title": "Step 3", + "intro": "Click the 'Create New Invoice' button", + "position": "bottom", + "element": "#invoice-section, .invoice-tab, #create-invoice" + }, + { + "title": "Step 4", + "intro": "Select a customer from the dropdown or click 'Add New Customer'", + "position": "bottom", + "element": "#customer-select, select[name='customer'], .customer-field" + }, + { + "title": "Step 5", + "intro": "Select the car(s) to include in the invoice", + "position": "bottom", + "element": "#invoice-section, .invoice-tab, #create-invoice" + }, + { + "title": "Step 6", + "intro": "Add any additional services or parts by clicking 'Add Item'", + "position": "bottom" + }, + { + "title": "Step 7", + "intro": "Set the payment terms and due date", + "position": "bottom" + }, + { + "title": "Step 8", + "intro": "Click 'Save Draft' to save without finalizing, or 'Finalize Invoice' to complete", + "position": "bottom", + "element": "button[type='submit'], .btn-save, #save-button" + } + ] +} \ No newline at end of file diff --git a/static/js/tours/help-button.js b/static/js/tours/help-button.js new file mode 100644 index 00000000..9081f7a7 --- /dev/null +++ b/static/js/tours/help-button.js @@ -0,0 +1,163 @@ +/** + * Help Button Component + * Provides context-aware help based on the current page + */ +class HelpButton { + constructor(options = {}) { + this.options = Object.assign({ + position: 'bottom-right', + icon: 'question-circle', + text: 'Help', + autoDetect: true + }, options); + + this.pageToTourMap = { + '/inventory/': 'inventory_overview', + '/inventory/add/': 'add_new_car', + '/inventory/edit/': 'edit_car', + '/finance/invoices/': 'manage_invoices', + '/finance/invoices/create/': 'create_new_invoice', + '/customers/': 'manage_customers', + '/customers/add/': 'add_new_customer' + }; + + this.render(); + this.attachEvents(); + } + + render() { + // Create the help button + const button = document.createElement('div'); + button.className = `help-button ${this.options.position}`; + button.innerHTML = ` + + `; + + // Add styles + const style = document.createElement('style'); + style.textContent = ` + .help-button { + position: fixed; + z-index: 1000; + } + .help-button.bottom-right { + bottom: 20px; + right: 20px; + } + .help-button.bottom-left { + bottom: 20px; + left: 20px; + } + .help-button.top-right { + top: 20px; + right: 20px; + } + .help-button.top-left { + top: 20px; + left: 20px; + } + `; + + document.head.appendChild(style); + document.body.appendChild(button); + } + + attachEvents() { + const helpButton = document.getElementById('context-help-btn'); + if (!helpButton) return; + + helpButton.addEventListener('click', () => { + this.showContextHelp(); + }); + + // Initialize tooltip + new bootstrap.Tooltip(helpButton); + } + + showContextHelp() { + // Detect current page and show appropriate tour + if (this.options.autoDetect) { + const currentPath = window.location.pathname; + let tourSlug = null; + + // Find the best match for the current path + for (const [path, slug] of Object.entries(this.pageToTourMap)) { + if (currentPath.includes(path)) { + tourSlug = slug; + break; + } + } + + if (tourSlug) { + window.tourManager.loadTour(tourSlug); + return; + } + } + + // If no specific tour found or autoDetect is off, show help menu + this.showHelpMenu(); + } + + showHelpMenu() { + // Create a modal with available help options + const modal = document.createElement('div'); + modal.className = 'modal fade'; + modal.id = 'helpModal'; + modal.setAttribute('tabindex', '-1'); + + modal.innerHTML = ` + + `; + + document.body.appendChild(modal); + + const modalInstance = new bootstrap.Modal(modal); + modalInstance.show(); + + // Remove modal from DOM after it's hidden + modal.addEventListener('hidden.bs.modal', () => { + modal.remove(); + }); + } +} + +// Initialize help button on all pages +document.addEventListener('DOMContentLoaded', () => { + window.helpButton = new HelpButton(); +}); + +// export { HelpButton }; diff --git a/static/js/tours/tour-manager.js b/static/js/tours/tour-manager.js new file mode 100644 index 00000000..ad0b910f --- /dev/null +++ b/static/js/tours/tour-manager.js @@ -0,0 +1,134 @@ +/** + * Tour Manager for Car Inventory System + * Uses IntroJS to provide guided tours of the application + */ + +function getCsrfToken(name) { + let cookieValue = null; + if (document.cookie && document.cookie !== "") { + const cookies = document.cookie.split(";"); + for (let cookie of cookies) { + cookie = cookie.trim(); + if (cookie.substring(0, name.length + 1) === name + "=") { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; + } + +class TourManager { + constructor() { + this.introJs = introJs(); + this.currentTour = null; + this.tourData = null; + this.tourSlug = null; + + // Configure IntroJS defaults + this.introJs.setOptions({ + showStepNumbers: true, + showBullets: true, + showProgress: true, + scrollToElement: true, + disableInteraction: false, + doneLabel: 'Finish', + nextLabel: 'Next →', + prevLabel: '← Back', + exitOnEsc: true, + exitOnOverlayClick: false + }); + + // Set up event listeners + this.introJs.oncomplete(() => this.onTourComplete()); + this.introJs.onexit(() => this.onTourExit()); + } + + /** + * Load and start a tour by its slug + * @param {string} slug - The tour slug + */ + async loadTour(slug) { + try { + this.tourSlug = slug; + const response = await fetch(`/tours/data/${slug}/`); + if (!response.ok) { + throw new Error('Failed to load tour data'); + } + + const data = await response.json(); + this.tourData = data.tour; + + // If user already completed this tour, ask if they want to repeat + if (data.completed && !confirm('You have already completed this guide. Would you like to view it again?')) { + return; + } + + this.startTour(); + } catch (error) { + console.error('Error loading tour:', error); + alert('Failed to load the interactive guide. Please try again later.'); + } + } + + /** + * Start the currently loaded tour + */ + startTour() { + if (!this.tourData) { + console.error('No tour data loaded'); + return; + } + + this.introJs.setOptions({ + steps: this.tourData.steps + }); + + this.introJs.start(); + } + + /** + * Handle tour completion + */ + onTourComplete() { + if (!this.tourSlug) return; + + // Mark the tour as completed on the server + fetch(`/tours/complete/${this.tourSlug}/`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': getCsrfToken() + } + }).catch(error => { + console.error('Error marking tour as completed:', error); + }); + + // Show success message + alert('Congratulations! You have completed the guide.'); + } + + /** + * Handle tour exit (without completion) + */ + onTourExit() { + console.log('Tour exited'); + } + + /** + * Get CSRF token from cookies + */ + +} + +// Initialize the tour manager +window.tourManager = new TourManager(); + +// Function to start a tour from a link +function startTour(slug) { + window.tourManager.loadTour(slug); + return false; // Prevent default link action +} + +// Export for use in other modules +// export { startTour }; \ No newline at end of file diff --git a/static/js/tours/ui_element_map.json b/static/js/tours/ui_element_map.json new file mode 100644 index 00000000..bf45bb76 --- /dev/null +++ b/static/js/tours/ui_element_map.json @@ -0,0 +1,2811 @@ +{ + "pages": { + "registration_password_reset_done": { + "template": "registration/password_reset_done.html", + "elements": {} + }, + "registration_password_change_form": { + "template": "registration/password_change_form.html", + "elements": { + "logout-form": "#logout-form", + "content-main": "#content-main", + "{{ form.new_password1.id_for_label }}_helptext": "#{{ form.new_password1.id_for_label }}_helptext", + "{{ form.new_password2.id_for_label }}_helptext": "#{{ form.new_password2.id_for_label }}_helptext" + } + }, + "registration_password_reset_email": { + "template": "registration/password_reset_email.html", + "elements": {} + }, + "registration_password_reset_confirm": { + "template": "registration/password_reset_confirm.html", + "elements": {} + }, + "registration_password_reset_form": { + "template": "registration/password_reset_form.html", + "elements": {} + }, + "registration_logged_out": { + "template": "registration/logged_out.html", + "elements": {} + }, + "registration_password_reset_complete": { + "template": "registration/password_reset_complete.html", + "elements": {} + }, + "registration_password_change_done": { + "template": "registration/password_change_done.html", + "elements": { + "logout-form": "#logout-form" + } + }, + "admin_delete_selected_confirmation": { + "template": "admin/delete_selected_confirmation.html", + "elements": {} + }, + "admin_change_list_object_tools": { + "template": "admin/change_list_object_tools.html", + "elements": {} + }, + "admin_delete_confirmation": { + "template": "admin/delete_confirmation.html", + "elements": { + "deleted-objects": "#deleted-objects" + } + }, + "admin_actions": { + "template": "admin/actions.html", + "elements": {} + }, + "admin_color_theme_toggle": { + "template": "admin/color_theme_toggle.html", + "elements": {} + }, + "admin_change_list_results": { + "template": "admin/change_list_results.html", + "elements": { + "result_list": "#result_list" + } + }, + "admin_index": { + "template": "admin/index.html", + "elements": { + "content-main": "#content-main", + "content-related": "#content-related", + "recent-actions-module": "#recent-actions-module" + } + }, + "admin_pagination": { + "template": "admin/pagination.html", + "elements": {} + }, + "admin_base": { + "template": "admin/base.html", + "elements": { + "container": "#container", + "header": "#header", + "branding": "#branding", + "user-tools": "#user-tools", + "logout-form": "#logout-form", + "main": "#main", + "content-start": "#content-start", + "content": "#content", + "footer": "#footer", + "icon-auto": "#icon-auto", + "icon-moon": "#icon-moon", + "icon-sun": "#icon-sun" + } + }, + "admin_date_hierarchy": { + "template": "admin/date_hierarchy.html", + "elements": {} + }, + "admin_invalid_setup": { + "template": "admin/invalid_setup.html", + "elements": {} + }, + "admin_app_index": { + "template": "admin/app_index.html", + "elements": {} + }, + "admin_500": { + "template": "admin/500.html", + "elements": {} + }, + "admin_object_history": { + "template": "admin/object_history.html", + "elements": { + "content-main": "#content-main", + "change-history": "#change-history" + } + }, + "admin_404": { + "template": "admin/404.html", + "elements": {} + }, + "admin_login": { + "template": "admin/login.html", + "elements": { + "content-main": "#content-main", + "login-form": "#login-form" + } + }, + "admin_change_list": { + "template": "admin/change_list.html", + "elements": { + "content-main": "#content-main", + "changelist": "#changelist", + "changelist-form": "#changelist-form", + "changelist-filter": "#changelist-filter", + "changelist-filter-header": "#changelist-filter-header", + "changelist-filter-extra-actions": "#changelist-filter-extra-actions" + } + }, + "admin_popup_response": { + "template": "admin/popup_response.html", + "elements": { + "django-admin-popup-response-constants": "#django-admin-popup-response-constants" + } + }, + "admin_change_form_object_tools": { + "template": "admin/change_form_object_tools.html", + "elements": {} + }, + "admin_app_list": { + "template": "admin/app_list.html", + "elements": { + "{{ app.app_label }}-{{ model_name }}": "#{{ app.app_label }}-{{ model_name }}" + } + }, + "admin_submit_line": { + "template": "admin/submit_line.html", + "elements": {} + }, + "admin_search_form": { + "template": "admin/search_form.html", + "elements": { + "toolbar": "#toolbar", + "changelist-search": "#changelist-search", + "searchbar": "#searchbar", + "searchbar_helptext": "#searchbar_helptext" + } + }, + "admin_prepopulated_fields_js": { + "template": "admin/prepopulated_fields_js.html", + "elements": { + "django-admin-prepopulated-fields-constants": "#django-admin-prepopulated-fields-constants" + } + }, + "admin_change_form": { + "template": "admin/change_form.html", + "elements": { + "content-main": "#content-main", + "{{ opts.model_name }}_form": "#{{ opts.model_name }}_form", + "django-admin-form-add-constants": "#django-admin-form-add-constants" + } + }, + "admin_nav_sidebar": { + "template": "admin/nav_sidebar.html", + "elements": { + "toggle-nav-sidebar": "#toggle-nav-sidebar", + "nav-sidebar": "#nav-sidebar", + "nav-filter": "#nav-filter" + } + }, + "admin_base_site": { + "template": "admin/base_site.html", + "elements": { + "site-name": "#site-name" + } + }, + "admin_filter": { + "template": "admin/filter.html", + "elements": {} + }, + "admin_auth_user_change_password": { + "template": "admin/auth/user/change_password.html", + "elements": { + "content-main": "#content-main", + "{{ opts.model_name }}_form": "#{{ opts.model_name }}_form", + "{{ form.usable_password.id_for_label }}_helptext": "#{{ form.usable_password.id_for_label }}_helptext", + "{{ form.password1.id_for_label }}_helptext": "#{{ form.password1.id_for_label }}_helptext", + "{{ form.password2.id_for_label }}_helptext": "#{{ form.password2.id_for_label }}_helptext" + } + }, + "admin_auth_user_add_form": { + "template": "admin/auth/user/add_form.html", + "elements": {} + }, + "admin_includes_object_delete_summary": { + "template": "admin/includes/object_delete_summary.html", + "elements": {} + }, + "admin_includes_fieldset": { + "template": "admin/includes/fieldset.html", + "elements": { + "{{ prefix }}-{{ id_prefix}}-{{ id_suffix }}-heading": "#{{ prefix }}-{{ id_prefix}}-{{ id_suffix }}-heading", + "{{ field.field.id_for_label }}_helptext": "#{{ field.field.id_for_label }}_helptext" + } + }, + "admin_edit_inline_stacked": { + "template": "admin/edit_inline/stacked.html", + "elements": { + "{{ inline_admin_formset.formset.prefix }}-group": "#{{ inline_admin_formset.formset.prefix }}-group", + "{{ inline_admin_formset.formset.prefix }}-heading": "#{{ inline_admin_formset.formset.prefix }}-heading", + "{{ inline_admin_formset.formset.prefix }}-{% if forloop.last and inline_admin_formset.has_add_permission %}empty{% else %}{{ forloop.counter0 }}{% endif %}": "#{{ inline_admin_formset.formset.prefix }}-{% if forloop.last and inline_admin_formset.has_add_permission %}empty{% else %}{{ forloop.counter0 }}{% endif %}" + } + }, + "admin_edit_inline_tabular": { + "template": "admin/edit_inline/tabular.html", + "elements": { + "{{ inline_admin_formset.formset.prefix }}-group": "#{{ inline_admin_formset.formset.prefix }}-group", + "{{ inline_admin_formset.formset.prefix }}-heading": "#{{ inline_admin_formset.formset.prefix }}-heading", + "{{ inline_admin_formset.formset.prefix }}-{% if forloop.last and inline_admin_formset.has_add_permission %}empty{% else %}{{ forloop.counter0 }}{% endif %}": "#{{ inline_admin_formset.formset.prefix }}-{% if forloop.last and inline_admin_formset.has_add_permission %}empty{% else %}{{ forloop.counter0 }}{% endif %}" + } + }, + "admin_widgets_many_to_many_raw_id": { + "template": "admin/widgets/many_to_many_raw_id.html", + "elements": {} + }, + "admin_widgets_radio": { + "template": "admin/widgets/radio.html", + "elements": {} + }, + "admin_widgets_url": { + "template": "admin/widgets/url.html", + "elements": {} + }, + "admin_widgets_foreign_key_raw_id": { + "template": "admin/widgets/foreign_key_raw_id.html", + "elements": { + "lookup_id_{{ widget.name }}": "#lookup_id_{{ widget.name }}" + } + }, + "admin_widgets_time": { + "template": "admin/widgets/time.html", + "elements": {} + }, + "admin_widgets_clearable_file_input": { + "template": "admin/widgets/clearable_file_input.html", + "elements": { + "{{ widget.checkbox_id }}": "#{{ widget.checkbox_id }}" + } + }, + "admin_widgets_split_datetime": { + "template": "admin/widgets/split_datetime.html", + "elements": {} + }, + "admin_widgets_date": { + "template": "admin/widgets/date.html", + "elements": {} + }, + "admin_widgets_related_widget_wrapper": { + "template": "admin/widgets/related_widget_wrapper.html", + "elements": { + "change_id_{{ name }}": "#change_id_{{ name }}", + "add_id_{{ name }}": "#add_id_{{ name }}", + "delete_id_{{ name }}": "#delete_id_{{ name }}", + "view_id_{{ name }}": "#view_id_{{ name }}" + } + }, + "auth_widgets_read_only_password_hash": { + "template": "auth/widgets/read_only_password_hash.html", + "elements": {} + }, + "usersessions_usersession_list": { + "template": "usersessions/usersession_list.html", + "elements": {} + }, + "usersessions_base_manage": { + "template": "usersessions/base_manage.html", + "elements": {} + }, + "allauth_elements_td": { + "template": "allauth/elements/td.html", + "elements": {} + }, + "allauth_elements_badge": { + "template": "allauth/elements/badge.html", + "elements": {} + }, + "allauth_elements_th": { + "template": "allauth/elements/th.html", + "elements": {} + }, + "allauth_elements_form": { + "template": "allauth/elements/form.html", + "elements": {} + }, + "allauth_elements_thead": { + "template": "allauth/elements/thead.html", + "elements": {} + }, + "allauth_elements_provider": { + "template": "allauth/elements/provider.html", + "elements": {} + }, + "allauth_elements_img": { + "template": "allauth/elements/img.html", + "elements": {} + }, + "allauth_elements_h2": { + "template": "allauth/elements/h2.html", + "elements": {} + }, + "allauth_elements_table": { + "template": "allauth/elements/table.html", + "elements": {} + }, + "allauth_elements_button": { + "template": "allauth/elements/button.html", + "elements": { + "{{ attrs.id }}": "#{{ attrs.id }}" + } + }, + "allauth_elements_p": { + "template": "allauth/elements/p.html", + "elements": {} + }, + "allauth_elements_details": { + "template": "allauth/elements/details.html", + "elements": {} + }, + "allauth_elements_button_group": { + "template": "allauth/elements/button_group.html", + "elements": {} + }, + "allauth_elements_fields": { + "template": "allauth/elements/fields.html", + "elements": {} + }, + "allauth_elements_hr": { + "template": "allauth/elements/hr.html", + "elements": {} + }, + "allauth_elements_alert": { + "template": "allauth/elements/alert.html", + "elements": {} + }, + "allauth_elements_h1": { + "template": "allauth/elements/h1.html", + "elements": {} + }, + "allauth_elements_panel": { + "template": "allauth/elements/panel.html", + "elements": {} + }, + "allauth_elements_field": { + "template": "allauth/elements/field.html", + "elements": { + "{{ attrs.id }}": "#{{ attrs.id }}" + } + }, + "allauth_elements_provider_list": { + "template": "allauth/elements/provider_list.html", + "elements": {} + }, + "allauth_elements_tbody": { + "template": "allauth/elements/tbody.html", + "elements": {} + }, + "allauth_elements_tr": { + "template": "allauth/elements/tr.html", + "elements": {} + }, + "allauth_layouts_base": { + "template": "allauth/layouts/base.html", + "elements": {} + }, + "allauth_layouts_manage": { + "template": "allauth/layouts/manage.html", + "elements": {} + }, + "allauth_layouts_entrance": { + "template": "allauth/layouts/entrance.html", + "elements": {} + }, + "tests_test_403_csrf": { + "template": "tests/test_403_csrf.html", + "elements": {} + }, + "mfa_index": { + "template": "mfa/index.html", + "elements": {} + }, + "mfa_base_entrance": { + "template": "mfa/base_entrance.html", + "elements": {} + }, + "mfa_reauthenticate": { + "template": "mfa/reauthenticate.html", + "elements": {} + }, + "mfa_base_manage": { + "template": "mfa/base_manage.html", + "elements": {} + }, + "mfa_trust": { + "template": "mfa/trust.html", + "elements": { + "logout-from-stage": "#logout-from-stage" + } + }, + "mfa_authenticate": { + "template": "mfa/authenticate.html", + "elements": { + "mfa_webauthn_authenticate": "#mfa_webauthn_authenticate", + "webauthn_form": "#webauthn_form", + "logout-from-stage": "#logout-from-stage" + } + }, + "mfa_recovery_codes_index": { + "template": "mfa/recovery_codes/index.html", + "elements": { + "recovery_codes": "#recovery_codes" + } + }, + "mfa_recovery_codes_base": { + "template": "mfa/recovery_codes/base.html", + "elements": {} + }, + "mfa_recovery_codes_generate": { + "template": "mfa/recovery_codes/generate.html", + "elements": {} + }, + "mfa_webauthn_signup_form": { + "template": "mfa/webauthn/signup_form.html", + "elements": { + "mfa_webauthn_signup": "#mfa_webauthn_signup", + "logout-from-stage": "#logout-from-stage" + } + }, + "mfa_webauthn_edit_form": { + "template": "mfa/webauthn/edit_form.html", + "elements": { + "mfa_webauthn_edit": "#mfa_webauthn_edit" + } + }, + "mfa_webauthn_base": { + "template": "mfa/webauthn/base.html", + "elements": {} + }, + "mfa_webauthn_reauthenticate": { + "template": "mfa/webauthn/reauthenticate.html", + "elements": { + "mfa_webauthn_reauthenticate": "#mfa_webauthn_reauthenticate" + } + }, + "mfa_webauthn_authenticator_list": { + "template": "mfa/webauthn/authenticator_list.html", + "elements": {} + }, + "mfa_webauthn_authenticator_confirm_delete": { + "template": "mfa/webauthn/authenticator_confirm_delete.html", + "elements": {} + }, + "mfa_webauthn_add_form": { + "template": "mfa/webauthn/add_form.html", + "elements": { + "mfa_webauthn_add": "#mfa_webauthn_add" + } + }, + "mfa_webauthn_snippets_scripts": { + "template": "mfa/webauthn/snippets/scripts.html", + "elements": {} + }, + "mfa_webauthn_snippets_login_script": { + "template": "mfa/webauthn/snippets/login_script.html", + "elements": { + "mfa_login": "#mfa_login", + "mfa_credential": "#mfa_credential" + } + }, + "mfa_totp_activate_form": { + "template": "mfa/totp/activate_form.html", + "elements": { + "authenticator_secret": "#authenticator_secret" + } + }, + "mfa_totp_base": { + "template": "mfa/totp/base.html", + "elements": {} + }, + "mfa_totp_deactivate_form": { + "template": "mfa/totp/deactivate_form.html", + "elements": {} + }, + "socialaccount_base_entrance": { + "template": "socialaccount/base_entrance.html", + "elements": {} + }, + "socialaccount_login_redirect": { + "template": "socialaccount/login_redirect.html", + "elements": {} + }, + "socialaccount_login": { + "template": "socialaccount/login.html", + "elements": {} + }, + "socialaccount_base_manage": { + "template": "socialaccount/base_manage.html", + "elements": {} + }, + "socialaccount_login_cancelled": { + "template": "socialaccount/login_cancelled.html", + "elements": {} + }, + "socialaccount_connections": { + "template": "socialaccount/connections.html", + "elements": {} + }, + "socialaccount_signup": { + "template": "socialaccount/signup.html", + "elements": {} + }, + "socialaccount_authentication_error": { + "template": "socialaccount/authentication_error.html", + "elements": {} + }, + "socialaccount_snippets_login": { + "template": "socialaccount/snippets/login.html", + "elements": {} + }, + "socialaccount_snippets_login_extra": { + "template": "socialaccount/snippets/login_extra.html", + "elements": {} + }, + "socialaccount_snippets_provider_list": { + "template": "socialaccount/snippets/provider_list.html", + "elements": {} + }, + "openid_base": { + "template": "openid/base.html", + "elements": {} + }, + "openid_login": { + "template": "openid/login.html", + "elements": {} + }, + "account_confirm_password_reset_code": { + "template": "account/confirm_password_reset_code.html", + "elements": {} + }, + "account_password_reset_from_key": { + "template": "account/password_reset_from_key.html", + "elements": { + "logout-from-stage": "#logout-from-stage" + } + }, + "account_signup_closed": { + "template": "account/signup_closed.html", + "elements": {} + }, + "account_base_reauthenticate": { + "template": "account/base_reauthenticate.html", + "elements": {} + }, + "account_request_login_code": { + "template": "account/request_login_code.html", + "elements": {} + }, + "account_password_reset_done": { + "template": "account/password_reset_done.html", + "elements": {} + }, + "account_email": { + "template": "account/email.html", + "elements": {} + }, + "account_base_entrance": { + "template": "account/base_entrance.html", + "elements": {} + }, + "account_verified_email_required": { + "template": "account/verified_email_required.html", + "elements": {} + }, + "account_reauthenticate": { + "template": "account/reauthenticate.html", + "elements": {} + }, + "account_password_reset_from_key_done": { + "template": "account/password_reset_from_key_done.html", + "elements": {} + }, + "account_email_confirm": { + "template": "account/email_confirm.html", + "elements": {} + }, + "account_account_inactive": { + "template": "account/account_inactive.html", + "elements": {} + }, + "account_phone_change": { + "template": "account/phone_change.html", + "elements": { + "current_phone": "#current_phone", + "verify-phone": "#verify-phone" + } + }, + "account_password_reset": { + "template": "account/password_reset.html", + "elements": {} + }, + "account_login": { + "template": "account/login.html", + "elements": { + "passkey_login": "#passkey_login" + } + }, + "account_email_change": { + "template": "account/email_change.html", + "elements": { + "current_email": "#current_email", + "new_email": "#new_email", + "pending-email": "#pending-email" + } + }, + "account_base_manage": { + "template": "account/base_manage.html", + "elements": {} + }, + "account_base_confirm_code": { + "template": "account/base_confirm_code.html", + "elements": { + "resend": "#resend", + "logout-from-stage": "#logout-from-stage" + } + }, + "account_verification_sent": { + "template": "account/verification_sent.html", + "elements": {} + }, + "account_confirm_email_verification_code": { + "template": "account/confirm_email_verification_code.html", + "elements": {} + }, + "account_confirm_phone_verification_code": { + "template": "account/confirm_phone_verification_code.html", + "elements": {} + }, + "account_base_manage_password": { + "template": "account/base_manage_password.html", + "elements": {} + }, + "account_base_manage_phone": { + "template": "account/base_manage_phone.html", + "elements": {} + }, + "account_logout": { + "template": "account/logout.html", + "elements": {} + }, + "account_password_change": { + "template": "account/password_change.html", + "elements": {} + }, + "account_signup_by_passkey": { + "template": "account/signup_by_passkey.html", + "elements": {} + }, + "account_password_set": { + "template": "account/password_set.html", + "elements": {} + }, + "account_confirm_login_code": { + "template": "account/confirm_login_code.html", + "elements": {} + }, + "account_signup": { + "template": "account/signup.html", + "elements": {} + }, + "account_base_manage_email": { + "template": "account/base_manage_email.html", + "elements": {} + }, + "account_snippets_warn_no_email": { + "template": "account/snippets/warn_no_email.html", + "elements": {} + }, + "account_snippets_already_logged_in": { + "template": "account/snippets/already_logged_in.html", + "elements": {} + }, + "debug_toolbar_redirect": { + "template": "debug_toolbar/redirect.html", + "elements": { + "redirect_to": "#redirect_to" + } + }, + "debug_toolbar_base": { + "template": "debug_toolbar/base.html", + "elements": { + "djDebug": "#djDebug", + "{{ toolbar.store_id }}": "#{{ toolbar.store_id }}", + "djDebugToolbar": "#djDebugToolbar", + "djDebugPanelList": "#djDebugPanelList", + "djHideToolBarButton": "#djHideToolBarButton", + "djToggleThemeButton": "#djToggleThemeButton", + "djDebugToolbarHandle": "#djDebugToolbarHandle", + "djShowToolBarButton": "#djShowToolBarButton", + "djShowToolBarD": "#djShowToolBarD", + "djShowToolBarJ": "#djShowToolBarJ", + "djDebugWindow": "#djDebugWindow" + } + }, + "debug_toolbar_panels_signals": { + "template": "debug_toolbar/panels/signals.html", + "elements": {} + }, + "debug_toolbar_panels_profiling": { + "template": "debug_toolbar/panels/profiling.html", + "elements": { + "profilingMain_{{ call.id }}": "#profilingMain_{{ call.id }}", + "{{ call.id }}": "#{{ call.id }}" + } + }, + "debug_toolbar_panels_sql_explain": { + "template": "debug_toolbar/panels/sql_explain.html", + "elements": {} + }, + "debug_toolbar_panels_staticfiles": { + "template": "debug_toolbar/panels/staticfiles.html", + "elements": {} + }, + "debug_toolbar_panels_cache": { + "template": "debug_toolbar/panels/cache.html", + "elements": { + "cacheMain_{{ forloop.counter }}": "#cacheMain_{{ forloop.counter }}", + "{{ forloop.counter }}": "#{{ forloop.counter }}", + "cacheDetails_{{ forloop.counter }}": "#cacheDetails_{{ forloop.counter }}" + } + }, + "debug_toolbar_panels_template_source": { + "template": "debug_toolbar/panels/template_source.html", + "elements": {} + }, + "debug_toolbar_panels_headers": { + "template": "debug_toolbar/panels/headers.html", + "elements": {} + }, + "debug_toolbar_panels_sql": { + "template": "debug_toolbar/panels/sql.html", + "elements": { + "sqlMain_{{ forloop.counter }}": "#sqlMain_{{ forloop.counter }}", + "{{ forloop.counter }}": "#{{ forloop.counter }}", + "sqlDetails_{{ forloop.counter }}": "#sqlDetails_{{ forloop.counter }}" + } + }, + "debug_toolbar_panels_timer": { + "template": "debug_toolbar/panels/timer.html", + "elements": { + "djDebugBrowserTiming": "#djDebugBrowserTiming", + "djDebugBrowserTimingTableBody": "#djDebugBrowserTimingTableBody" + } + }, + "debug_toolbar_panels_templates": { + "template": "debug_toolbar/panels/templates.html", + "elements": {} + }, + "debug_toolbar_panels_history": { + "template": "debug_toolbar/panels/history.html", + "elements": { + "djdtHistoryRequests": "#djdtHistoryRequests" + } + }, + "debug_toolbar_panels_versions": { + "template": "debug_toolbar/panels/versions.html", + "elements": {} + }, + "debug_toolbar_panels_request_variables": { + "template": "debug_toolbar/panels/request_variables.html", + "elements": {} + }, + "debug_toolbar_panels_sql_select": { + "template": "debug_toolbar/panels/sql_select.html", + "elements": {} + }, + "debug_toolbar_panels_settings": { + "template": "debug_toolbar/panels/settings.html", + "elements": {} + }, + "debug_toolbar_panels_alerts": { + "template": "debug_toolbar/panels/alerts.html", + "elements": {} + }, + "debug_toolbar_panels_history_tr": { + "template": "debug_toolbar/panels/history_tr.html", + "elements": { + "historyMain_{{ id }}": "#historyMain_{{ id }}", + "{{ id }}": "#{{ id }}" + } + }, + "debug_toolbar_panels_request": { + "template": "debug_toolbar/panels/request.html", + "elements": {} + }, + "debug_toolbar_panels_sql_profile": { + "template": "debug_toolbar/panels/sql_profile.html", + "elements": {} + }, + "debug_toolbar_includes_theme_selector": { + "template": "debug_toolbar/includes/theme_selector.html", + "elements": {} + }, + "debug_toolbar_includes_panel_button": { + "template": "debug_toolbar/includes/panel_button.html", + "elements": { + "djdt-{{ panel.panel_id }}": "#djdt-{{ panel.panel_id }}" + } + }, + "debug_toolbar_includes_panel_content": { + "template": "debug_toolbar/includes/panel_content.html", + "elements": { + "{{ panel.panel_id }}": "#{{ panel.panel_id }}" + } + }, + "silk_sql_detail": { + "template": "silk/sql_detail.html", + "elements": { + "query-div": "#query-div", + "query": "#query", + "query-info-div": "#query-info-div", + "time-taken-div": "#time-taken-div", + "num-joins-div": "#num-joins-div", + "query-plan-div": "#query-plan-div", + "plan": "#plan", + "traceback": "#traceback" + } + }, + "silk_profiling": { + "template": "silk/profiling.html", + "elements": { + "filter-form": "#filter-form" + } + }, + "silk_clear_db": { + "template": "silk/clear_db.html", + "elements": {} + }, + "silk_profile_detail": { + "template": "silk/profile_detail.html", + "elements": { + "query-div": "#query-div", + "query-info-div": "#query-info-div", + "code": "#code", + "error-div": "#error-div", + "percent": "#percent", + "graph-div": "#graph-div", + "pyprofile-div": "#pyprofile-div" + } + }, + "silk_requests": { + "template": "silk/requests.html", + "elements": { + "filter-form": "#filter-form" + } + }, + "silk_sql": { + "template": "silk/sql.html", + "elements": { + "query-div": "#query-div", + "query-info-div": "#query-info-div", + "table-div": "#table-div", + "table-pagination": "#table-pagination" + } + }, + "silk_cprofile": { + "template": "silk/cprofile.html", + "elements": { + "query-div": "#query-div", + "pyprofile-div": "#pyprofile-div" + } + }, + "silk_raw": { + "template": "silk/raw.html", + "elements": {} + }, + "silk_summary": { + "template": "silk/summary.html", + "elements": { + "filters": "#filters", + "filter-form": "#filter-form", + "filter-table": "#filter-table", + "filter-image": "#filter-image", + "filter-cell": "#filter-cell" + } + }, + "silk_request": { + "template": "silk/request.html", + "elements": { + "request-summary": "#request-summary", + "request-info": "#request-info", + "pre-curl": "#pre-curl" + } + }, + "silk_inclusion_request_summary_row": { + "template": "silk/inclusion/request_summary_row.html", + "elements": {} + }, + "silk_inclusion_profile_summary": { + "template": "silk/inclusion/profile_summary.html", + "elements": {} + }, + "silk_inclusion_heading": { + "template": "silk/inclusion/heading.html", + "elements": {} + }, + "silk_inclusion_request_menu": { + "template": "silk/inclusion/request_menu.html", + "elements": {} + }, + "silk_inclusion_profile_menu": { + "template": "silk/inclusion/profile_menu.html", + "elements": {} + }, + "silk_inclusion_request_summary": { + "template": "silk/inclusion/request_summary.html", + "elements": {} + }, + "silk_inclusion_code": { + "template": "silk/inclusion/code.html", + "elements": {} + }, + "silk_inclusion_root_menu": { + "template": "silk/inclusion/root_menu.html", + "elements": {} + }, + "silk_base_base": { + "template": "silk/base/base.html", + "elements": { + "content": "#content", + "header": "#header", + "filter": "#filter", + "data": "#data" + } + }, + "silk_base_root_base": { + "template": "silk/base/root_base.html", + "elements": { + "filter-button": "#filter-button", + "filter-item": "#filter-item", + "num-filters": "#num-filters", + "cbp-spmenu-s2": "#cbp-spmenu-s2", + "filter-form2": "#filter-form2" + } + }, + "silk_base_detail_base": { + "template": "silk/base/detail_base.html", + "elements": {} + }, + "django_tables2_bootstrap": { + "template": "django_tables2/bootstrap.html", + "elements": {} + }, + "django_tables2_semantic": { + "template": "django_tables2/semantic.html", + "elements": {} + }, + "django_tables2_table": { + "template": "django_tables2/table.html", + "elements": {} + }, + "django_tables2_bootstrap4-responsive": { + "template": "django_tables2/bootstrap4-responsive.html", + "elements": {} + }, + "django_tables2_bootstrap-responsive": { + "template": "django_tables2/bootstrap-responsive.html", + "elements": {} + }, + "django_tables2_bootstrap5": { + "template": "django_tables2/bootstrap5.html", + "elements": {} + }, + "django_tables2_bootstrap4": { + "template": "django_tables2/bootstrap4.html", + "elements": {} + }, + "django_tables2_bootstrap5-responsive": { + "template": "django_tables2/bootstrap5-responsive.html", + "elements": {} + }, + "django_bootstrap5_pagination": { + "template": "django_bootstrap5/pagination.html", + "elements": {} + }, + "django_bootstrap5_field_errors": { + "template": "django_bootstrap5/field_errors.html", + "elements": {} + }, + "django_bootstrap5_form_errors": { + "template": "django_bootstrap5/form_errors.html", + "elements": {} + }, + "django_bootstrap5_messages": { + "template": "django_bootstrap5/messages.html", + "elements": {} + }, + "django_bootstrap5_field_help_text": { + "template": "django_bootstrap5/field_help_text.html", + "elements": { + "{{ id_help_text }}": "#{{ id_help_text }}" + } + }, + "django_bootstrap5_bootstrap5": { + "template": "django_bootstrap5/bootstrap5.html", + "elements": {} + }, + "django_bootstrap5_widgets_radio_select": { + "template": "django_bootstrap5/widgets/radio_select.html", + "elements": { + "{{ option.attrs.id }}": "#{{ option.attrs.id }}" + } + }, + "django_bootstrap5_widgets_clearable_file_input": { + "template": "django_bootstrap5/widgets/clearable_file_input.html", + "elements": { + "{{ widget.checkbox_id }}": "#{{ widget.checkbox_id }}" + } + }, + "django_bootstrap5_widgets_radio_select_button_group": { + "template": "django_bootstrap5/widgets/radio_select_button_group.html", + "elements": { + "{{ widget.attrs.id }}": "#{{ widget.attrs.id }}", + "{{ option.attrs.id }}": "#{{ option.attrs.id }}" + } + }, + "bootstrap5_whole_uni_form": { + "template": "bootstrap5/whole_uni_form.html", + "elements": {} + }, + "bootstrap5_display_form": { + "template": "bootstrap5/display_form.html", + "elements": {} + }, + "bootstrap5_uni_form": { + "template": "bootstrap5/uni_form.html", + "elements": {} + }, + "bootstrap5_uni_formset": { + "template": "bootstrap5/uni_formset.html", + "elements": {} + }, + "bootstrap5_accordion": { + "template": "bootstrap5/accordion.html", + "elements": { + "{{ accordion.css_id }}": "#{{ accordion.css_id }}" + } + }, + "bootstrap5_table_inline_formset": { + "template": "bootstrap5/table_inline_formset.html", + "elements": { + "{{ form_id }}_table": "#{{ form_id }}_table" + } + }, + "bootstrap5_accordion-group": { + "template": "bootstrap5/accordion-group.html", + "elements": { + "{{ div.css_id }}": "#{{ div.css_id }}" + } + }, + "bootstrap5_inputs": { + "template": "bootstrap5/inputs.html", + "elements": {} + }, + "bootstrap5_betterform": { + "template": "bootstrap5/betterform.html", + "elements": {} + }, + "bootstrap5_whole_uni_formset": { + "template": "bootstrap5/whole_uni_formset.html", + "elements": {} + }, + "bootstrap5_errors": { + "template": "bootstrap5/errors.html", + "elements": {} + }, + "bootstrap5_field": { + "template": "bootstrap5/field.html", + "elements": { + "div_{{ field.auto_id }}": "#div_{{ field.auto_id }}" + } + }, + "bootstrap5_errors_formset": { + "template": "bootstrap5/errors_formset.html", + "elements": {} + }, + "bootstrap5_layout_inline_field": { + "template": "bootstrap5/layout/inline_field.html", + "elements": { + "div_{{ field.auto_id }}": "#div_{{ field.auto_id }}" + } + }, + "bootstrap5_layout_help_text_and_errors": { + "template": "bootstrap5/layout/help_text_and_errors.html", + "elements": {} + }, + "bootstrap5_layout_radioselect_inline": { + "template": "bootstrap5/layout/radioselect_inline.html", + "elements": { + "div_{{ field.auto_id }}": "#div_{{ field.auto_id }}" + } + }, + "bootstrap5_layout_field_errors": { + "template": "bootstrap5/layout/field_errors.html", + "elements": { + "{{field.errors.field_id}}_error": "#{{field.errors.field_id}}_error", + "{{field.auto_id}}_error": "#{{field.auto_id}}_error", + "error_{{ forloop.counter }}_{{ field.auto_id }}": "#error_{{ forloop.counter }}_{{ field.auto_id }}" + } + }, + "bootstrap5_layout_uneditable_input": { + "template": "bootstrap5/layout/uneditable_input.html", + "elements": { + "div_{{ field.auto_id }}": "#div_{{ field.auto_id }}" + } + }, + "bootstrap5_layout_checkboxselectmultiple_inline": { + "template": "bootstrap5/layout/checkboxselectmultiple_inline.html", + "elements": { + "div_{{ field.auto_id }}": "#div_{{ field.auto_id }}" + } + }, + "bootstrap5_layout_baseinput": { + "template": "bootstrap5/layout/baseinput.html", + "elements": { + "{% if input.id %}{{ input.id }}{% else %}{{ input.input_type }}-id-{{ input.name|slugify }}{% endif %}": "#{% if input.id %}{{ input.id }}{% else %}{{ input.input_type }}-id-{{ input.name|slugify }}{% endif %}" + } + }, + "bootstrap5_layout_help_text": { + "template": "bootstrap5/layout/help_text.html", + "elements": { + "{{ field.auto_id }}_helptext": "#{{ field.auto_id }}_helptext" + } + }, + "bootstrap5_layout_row": { + "template": "bootstrap5/layout/row.html", + "elements": { + "{{ div.css_id }}": "#{{ div.css_id }}" + } + }, + "bootstrap5_layout_button": { + "template": "bootstrap5/layout/button.html", + "elements": {} + }, + "bootstrap5_layout_field_errors_block": { + "template": "bootstrap5/layout/field_errors_block.html", + "elements": { + "{{field.errors.field_id}}_error": "#{{field.errors.field_id}}_error", + "{{field.auto_id}}_error": "#{{field.auto_id}}_error", + "error_{{ forloop.counter }}_{{ field.auto_id }}": "#error_{{ forloop.counter }}_{{ field.auto_id }}" + } + }, + "bootstrap5_layout_field_with_buttons": { + "template": "bootstrap5/layout/field_with_buttons.html", + "elements": { + "{{ div.css_id }}": "#{{ div.css_id }}", + "{{field.errors.field_id}}_error": "#{{field.errors.field_id}}_error", + "{{field.auto_id}}_error": "#{{field.auto_id}}_error", + "error_{{ forloop.counter }}_{{ field.auto_id }}": "#error_{{ forloop.counter }}_{{ field.auto_id }}" + } + }, + "bootstrap5_layout_buttonholder": { + "template": "bootstrap5/layout/buttonholder.html", + "elements": { + "{{ buttonholder.css_id }}": "#{{ buttonholder.css_id }}" + } + }, + "bootstrap5_layout_div": { + "template": "bootstrap5/layout/div.html", + "elements": { + "{{ div.css_id }}": "#{{ div.css_id }}" + } + }, + "bootstrap5_layout_tab-link": { + "template": "bootstrap5/layout/tab-link.html", + "elements": {} + }, + "bootstrap5_layout_alert": { + "template": "bootstrap5/layout/alert.html", + "elements": { + "{{ alert.css_id }}": "#{{ alert.css_id }}" + } + }, + "bootstrap5_layout_radio_checkbox_select": { + "template": "bootstrap5/layout/radio_checkbox_select.html", + "elements": { + "{{field.errors.field_id}}_error": "#{{field.errors.field_id}}_error", + "{{field.auto_id}}_error": "#{{field.auto_id}}_error", + "error_{{ forloop.counter }}_{{ field.auto_id }}": "#error_{{ forloop.counter }}_{{ field.auto_id }}" + } + }, + "bootstrap5_layout_column": { + "template": "bootstrap5/layout/column.html", + "elements": { + "{{ div.css_id }}": "#{{ div.css_id }}" + } + }, + "bootstrap5_layout_prepended_appended_text": { + "template": "bootstrap5/layout/prepended_appended_text.html", + "elements": { + "div_{{ field.auto_id }}": "#div_{{ field.auto_id }}" + } + }, + "bootstrap5_layout_formactions": { + "template": "bootstrap5/layout/formactions.html", + "elements": { + "{{ formactions.id }}": "#{{ formactions.id }}" + } + }, + "bootstrap5_layout_fieldset": { + "template": "bootstrap5/layout/fieldset.html", + "elements": { + "{{ fieldset.css_id }}": "#{{ fieldset.css_id }}" + } + }, + "bootstrap5_layout_multifield": { + "template": "bootstrap5/layout/multifield.html", + "elements": {} + }, + "bootstrap5_layout_modal": { + "template": "bootstrap5/layout/modal.html", + "elements": { + "{{ modal.css_id }}": "#{{ modal.css_id }}", + "{{ modal.title_id }}-label": "#{{ modal.title_id }}-label" + } + }, + "bootstrap5_layout_field_file": { + "template": "bootstrap5/layout/field_file.html", + "elements": { + "{{ widget.data.checkbox_id }}": "#{{ widget.data.checkbox_id }}" + } + }, + "bootstrap5_layout_floating_field": { + "template": "bootstrap5/layout/floating_field.html", + "elements": { + "div_{{ field.auto_id }}": "#div_{{ field.auto_id }}" + } + }, + "bootstrap5_layout_attrs": { + "template": "bootstrap5/layout/attrs.html", + "elements": {} + }, + "bootstrap5_layout_tab": { + "template": "bootstrap5/layout/tab.html", + "elements": { + "{{ tabs.css_id }}": "#{{ tabs.css_id }}" + } + }, + "bootstrap5_layout_switch": { + "template": "bootstrap5/layout/switch.html", + "elements": { + "div_{{ field.auto_id }}": "#div_{{ field.auto_id }}" + } + }, + "rest_framework_admin": { + "template": "rest_framework/admin.html", + "elements": { + "content": "#content", + "get-form": "#get-form", + "extra-actions-menu": "#extra-actions-menu", + "createModal": "#createModal", + "myModalLabel": "#myModalLabel", + "editModal": "#editModal", + "errorModal": "#errorModal", + "drf_csrf": "#drf_csrf" + } + }, + "rest_framework_base": { + "template": "rest_framework/base.html", + "elements": { + "content": "#content", + "get-form": "#get-form", + "deleteModal": "#deleteModal", + "extra-actions-menu": "#extra-actions-menu", + "post-object-form": "#post-object-form", + "post-generic-content-form": "#post-generic-content-form", + "put-object-form": "#put-object-form", + "put-generic-content-form": "#put-generic-content-form", + "drf_csrf": "#drf_csrf" + } + }, + "rest_framework_raw_data_form": { + "template": "rest_framework/raw_data_form.html", + "elements": {} + }, + "rest_framework_login": { + "template": "rest_framework/login.html", + "elements": {} + }, + "rest_framework_login_base": { + "template": "rest_framework/login_base.html", + "elements": { + "div_id_username": "#div_id_username", + "id_username": "#id_username", + "div_id_password": "#div_id_password", + "id_password": "#id_password", + "submit-id-submit": "#submit-id-submit" + } + }, + "rest_framework_api": { + "template": "rest_framework/api.html", + "elements": {} + }, + "rest_framework_filters_base": { + "template": "rest_framework/filters/base.html", + "elements": { + "filtersModal": "#filtersModal" + } + }, + "rest_framework_filters_search": { + "template": "rest_framework/filters/search.html", + "elements": {} + }, + "rest_framework_filters_ordering": { + "template": "rest_framework/filters/ordering.html", + "elements": {} + }, + "rest_framework_pagination_numbers": { + "template": "rest_framework/pagination/numbers.html", + "elements": {} + }, + "rest_framework_pagination_previous_and_next": { + "template": "rest_framework/pagination/previous_and_next.html", + "elements": {} + }, + "rest_framework_horizontal_select": { + "template": "rest_framework/horizontal/select.html", + "elements": {} + }, + "rest_framework_horizontal_dict_field": { + "template": "rest_framework/horizontal/dict_field.html", + "elements": {} + }, + "rest_framework_horizontal_form": { + "template": "rest_framework/horizontal/form.html", + "elements": {} + }, + "rest_framework_horizontal_list_fieldset": { + "template": "rest_framework/horizontal/list_fieldset.html", + "elements": {} + }, + "rest_framework_horizontal_textarea": { + "template": "rest_framework/horizontal/textarea.html", + "elements": {} + }, + "rest_framework_horizontal_radio": { + "template": "rest_framework/horizontal/radio.html", + "elements": {} + }, + "rest_framework_horizontal_checkbox_multiple": { + "template": "rest_framework/horizontal/checkbox_multiple.html", + "elements": {} + }, + "rest_framework_horizontal_checkbox": { + "template": "rest_framework/horizontal/checkbox.html", + "elements": {} + }, + "rest_framework_horizontal_fieldset": { + "template": "rest_framework/horizontal/fieldset.html", + "elements": {} + }, + "rest_framework_horizontal_input": { + "template": "rest_framework/horizontal/input.html", + "elements": {} + }, + "rest_framework_horizontal_select_multiple": { + "template": "rest_framework/horizontal/select_multiple.html", + "elements": {} + }, + "rest_framework_horizontal_list_field": { + "template": "rest_framework/horizontal/list_field.html", + "elements": {} + }, + "rest_framework_admin_dict_value": { + "template": "rest_framework/admin/dict_value.html", + "elements": {} + }, + "rest_framework_admin_list": { + "template": "rest_framework/admin/list.html", + "elements": {} + }, + "rest_framework_admin_simple_list_value": { + "template": "rest_framework/admin/simple_list_value.html", + "elements": {} + }, + "rest_framework_admin_detail": { + "template": "rest_framework/admin/detail.html", + "elements": {} + }, + "rest_framework_admin_list_value": { + "template": "rest_framework/admin/list_value.html", + "elements": {} + }, + "rest_framework_docs_link": { + "template": "rest_framework/docs/link.html", + "elements": { + "{{ section_key }}-{{ link_key|slugify }}": "#{{ section_key }}-{{ link_key|slugify }}" + } + }, + "rest_framework_docs_index": { + "template": "rest_framework/docs/index.html", + "elements": { + "main": "#main" + } + }, + "rest_framework_docs_document": { + "template": "rest_framework/docs/document.html", + "elements": { + "{{ section_key }}": "#{{ section_key }}" + } + }, + "rest_framework_docs_interact": { + "template": "rest_framework/docs/interact.html", + "elements": { + "{{ section_key }}_{{ link_key|slugify }}_modal": "#{{ section_key }}_{{ link_key|slugify }}_modal", + "response": "#response" + } + }, + "rest_framework_docs_sidebar": { + "template": "rest_framework/docs/sidebar.html", + "elements": { + "menu-content": "#menu-content", + "{{ section_key }}-dropdown": "#{{ section_key }}-dropdown", + "selected-authentication": "#selected-authentication", + "auth-control": "#auth-control", + "selected-language": "#selected-language", + "language-control": "#language-control" + } + }, + "rest_framework_docs_error": { + "template": "rest_framework/docs/error.html", + "elements": {} + }, + "rest_framework_docs_auth_basic": { + "template": "rest_framework/docs/auth/basic.html", + "elements": { + "auth_basic_modal": "#auth_basic_modal", + "username": "#username", + "password": "#password" + } + }, + "rest_framework_docs_auth_token": { + "template": "rest_framework/docs/auth/token.html", + "elements": { + "auth_token_modal": "#auth_token_modal", + "scheme": "#scheme", + "schemeHelpBlock": "#schemeHelpBlock", + "token": "#token", + "tokenHelpBlock": "#tokenHelpBlock" + } + }, + "rest_framework_docs_auth_session": { + "template": "rest_framework/docs/auth/session.html", + "elements": { + "auth_session_modal": "#auth_session_modal" + } + }, + "rest_framework_docs_langs_python": { + "template": "rest_framework/docs/langs/python.html", + "elements": {} + }, + "rest_framework_docs_langs_javascript": { + "template": "rest_framework/docs/langs/javascript.html", + "elements": {} + }, + "rest_framework_docs_langs_shell": { + "template": "rest_framework/docs/langs/shell.html", + "elements": {} + }, + "rest_framework_docs_langs_shell-intro": { + "template": "rest_framework/docs/langs/shell-intro.html", + "elements": {} + }, + "rest_framework_docs_langs_python-intro": { + "template": "rest_framework/docs/langs/python-intro.html", + "elements": {} + }, + "rest_framework_docs_langs_javascript-intro": { + "template": "rest_framework/docs/langs/javascript-intro.html", + "elements": {} + }, + "rest_framework_inline_select": { + "template": "rest_framework/inline/select.html", + "elements": {} + }, + "rest_framework_inline_dict_field": { + "template": "rest_framework/inline/dict_field.html", + "elements": {} + }, + "rest_framework_inline_form": { + "template": "rest_framework/inline/form.html", + "elements": {} + }, + "rest_framework_inline_list_fieldset": { + "template": "rest_framework/inline/list_fieldset.html", + "elements": {} + }, + "rest_framework_inline_textarea": { + "template": "rest_framework/inline/textarea.html", + "elements": {} + }, + "rest_framework_inline_radio": { + "template": "rest_framework/inline/radio.html", + "elements": {} + }, + "rest_framework_inline_checkbox_multiple": { + "template": "rest_framework/inline/checkbox_multiple.html", + "elements": {} + }, + "rest_framework_inline_checkbox": { + "template": "rest_framework/inline/checkbox.html", + "elements": {} + }, + "rest_framework_inline_fieldset": { + "template": "rest_framework/inline/fieldset.html", + "elements": {} + }, + "rest_framework_inline_input": { + "template": "rest_framework/inline/input.html", + "elements": {} + }, + "rest_framework_inline_select_multiple": { + "template": "rest_framework/inline/select_multiple.html", + "elements": {} + }, + "rest_framework_inline_list_field": { + "template": "rest_framework/inline/list_field.html", + "elements": {} + }, + "rest_framework_vertical_select": { + "template": "rest_framework/vertical/select.html", + "elements": {} + }, + "rest_framework_vertical_dict_field": { + "template": "rest_framework/vertical/dict_field.html", + "elements": {} + }, + "rest_framework_vertical_form": { + "template": "rest_framework/vertical/form.html", + "elements": {} + }, + "rest_framework_vertical_list_fieldset": { + "template": "rest_framework/vertical/list_fieldset.html", + "elements": {} + }, + "rest_framework_vertical_textarea": { + "template": "rest_framework/vertical/textarea.html", + "elements": {} + }, + "rest_framework_vertical_radio": { + "template": "rest_framework/vertical/radio.html", + "elements": {} + }, + "rest_framework_vertical_checkbox_multiple": { + "template": "rest_framework/vertical/checkbox_multiple.html", + "elements": {} + }, + "rest_framework_vertical_checkbox": { + "template": "rest_framework/vertical/checkbox.html", + "elements": {} + }, + "rest_framework_vertical_fieldset": { + "template": "rest_framework/vertical/fieldset.html", + "elements": {} + }, + "rest_framework_vertical_input": { + "template": "rest_framework/vertical/input.html", + "elements": {} + }, + "rest_framework_vertical_select_multiple": { + "template": "rest_framework/vertical/select_multiple.html", + "elements": {} + }, + "rest_framework_vertical_list_field": { + "template": "rest_framework/vertical/list_field.html", + "elements": {} + }, + "django_extensions_widgets_foreignkey_searchinput": { + "template": "django_extensions/widgets/foreignkey_searchinput.html", + "elements": { + "lookup_{{ name }}": "#lookup_{{ name }}", + "lookup_id_{{ name }}": "#lookup_id_{{ name }}" + } + }, + "django_ledger_closing_entry_closing_entry_update": { + "template": "django_ledger/closing_entry/closing_entry_update.html", + "elements": {} + }, + "django_ledger_closing_entry_closing_entry_create": { + "template": "django_ledger/closing_entry/closing_entry_create.html", + "elements": { + "djl-closing-entry-create-button": "#djl-closing-entry-create-button", + "djl-bill-create-back-button": "#djl-bill-create-back-button" + } + }, + "django_ledger_closing_entry_closing_entry_list": { + "template": "django_ledger/closing_entry/closing_entry_list.html", + "elements": {} + }, + "django_ledger_closing_entry_closing_entry_detail": { + "template": "django_ledger/closing_entry/closing_entry_detail.html", + "elements": {} + }, + "django_ledger_closing_entry_closing_entry_delete": { + "template": "django_ledger/closing_entry/closing_entry_delete.html", + "elements": {} + }, + "django_ledger_closing_entry_includes_card_closing_entry": { + "template": "django_ledger/closing_entry/includes/card_closing_entry.html", + "elements": { + "djl-bill-card-widget": "#djl-bill-card-widget", + "{{ closing_entry_model.get_mark_as_posted_html_id }}-button": "#{{ closing_entry_model.get_mark_as_posted_html_id }}-button", + "{{ closing_entry_model.get_update_transactions_html_id }}-button": "#{{ closing_entry_model.get_update_transactions_html_id }}-button", + "{{ closing_entry_model.get_mark_as_unposted_html_id }}-button": "#{{ closing_entry_model.get_mark_as_unposted_html_id }}-button", + "{{ closing_entry_model.get_delete_html_id }}-button": "#{{ closing_entry_model.get_delete_html_id }}-button" + } + }, + "django_ledger_closing_entry_tags_closing_entry_table": { + "template": "django_ledger/closing_entry/tags/closing_entry_table.html", + "elements": { + "{{ closing_entry_model.get_html_id }}": "#{{ closing_entry_model.get_html_id }}", + "closing-entry-action-{{ closing_entry_model.uuid }}": "#closing-entry-action-{{ closing_entry_model.uuid }}", + "dropdown-menu-{{ closing_entry_model.uuid }}": "#dropdown-menu-{{ closing_entry_model.uuid }}" + } + }, + "django_ledger_closing_entry_tags_closing_entry_txs_table": { + "template": "django_ledger/closing_entry/tags/closing_entry_txs_table.html", + "elements": { + "{{ ce_tx.get_html_id }}": "#{{ ce_tx.get_html_id }}" + } + }, + "django_ledger_expense_expense_list": { + "template": "django_ledger/expense/expense_list.html", + "elements": {} + }, + "django_ledger_expense_expense_create": { + "template": "django_ledger/expense/expense_create.html", + "elements": {} + }, + "django_ledger_expense_expense_update": { + "template": "django_ledger/expense/expense_update.html", + "elements": {} + }, + "django_ledger_expense_tags_expense_item_table": { + "template": "django_ledger/expense/tags/expense_item_table.html", + "elements": { + "invoice-action-{{ invoice.uuid }}": "#invoice-action-{{ invoice.uuid }}", + "dropdown-menu-{{ expense_item.uuid }}": "#dropdown-menu-{{ expense_item.uuid }}" + } + }, + "django_ledger_unit_unit_update": { + "template": "django_ledger/unit/unit_update.html", + "elements": {} + }, + "django_ledger_unit_unit_create": { + "template": "django_ledger/unit/unit_create.html", + "elements": {} + }, + "django_ledger_unit_unit_list": { + "template": "django_ledger/unit/unit_list.html", + "elements": {} + }, + "django_ledger_unit_unit_detail": { + "template": "django_ledger/unit/unit_detail.html", + "elements": {} + }, + "django_ledger_ledger_ledger_create": { + "template": "django_ledger/ledger/ledger_create.html", + "elements": {} + }, + "django_ledger_ledger_ledger_update": { + "template": "django_ledger/ledger/ledger_update.html", + "elements": {} + }, + "django_ledger_ledger_ledger_delete": { + "template": "django_ledger/ledger/ledger_delete.html", + "elements": {} + }, + "django_ledger_ledger_ledger_list": { + "template": "django_ledger/ledger/ledger_list.html", + "elements": {} + }, + "django_ledger_ledger_tags_ledgers_table": { + "template": "django_ledger/ledger/tags/ledgers_table.html", + "elements": { + "dropdown-menu5": "#dropdown-menu5", + "ledger-action-{{ ledger_model.uuid }}": "#ledger-action-{{ ledger_model.uuid }}", + "dropdown-menu-{{ ledger_model.uuid }}": "#dropdown-menu-{{ ledger_model.uuid }}" + } + }, + "django_ledger_auth_login": { + "template": "django_ledger/auth/login.html", + "elements": { + "djl-login-bg-image": "#djl-login-bg-image", + "djl-el-login-form": "#djl-el-login-form" + } + }, + "django_ledger_entity_entity_delete": { + "template": "django_ledger/entity/entity_delete.html", + "elements": {} + }, + "django_ledger_entity_home": { + "template": "django_ledger/entity/home.html", + "elements": {} + }, + "django_ledger_entity_entity_create": { + "template": "django_ledger/entity/entity_create.html", + "elements": {} + }, + "django_ledger_entity_entitiy_list": { + "template": "django_ledger/entity/entitiy_list.html", + "elements": {} + }, + "django_ledger_entity_entity_update": { + "template": "django_ledger/entity/entity_update.html", + "elements": {} + }, + "django_ledger_entity_entity_dashboard": { + "template": "django_ledger/entity/entity_dashboard.html", + "elements": {} + }, + "django_ledger_entity_includes_card_entity": { + "template": "django_ledger/entity/includes/card_entity.html", + "elements": {} + }, + "django_ledger_financial_statements_cash_flow": { + "template": "django_ledger/financial_statements/cash_flow.html", + "elements": {} + }, + "django_ledger_financial_statements_income_statement": { + "template": "django_ledger/financial_statements/income_statement.html", + "elements": {} + }, + "django_ledger_financial_statements_balance_sheet": { + "template": "django_ledger/financial_statements/balance_sheet.html", + "elements": {} + }, + "django_ledger_financial_statements_tags_balance_sheet_statement": { + "template": "django_ledger/financial_statements/tags/balance_sheet_statement.html", + "elements": { + "account-action-{{ account.uuid }}": "#account-action-{{ account.uuid }}", + "dropdown-menu-{{ acc.uuid }}": "#dropdown-menu-{{ acc.uuid }}" + } + }, + "django_ledger_financial_statements_tags_cash_flow_statement": { + "template": "django_ledger/financial_statements/tags/cash_flow_statement.html", + "elements": {} + }, + "django_ledger_financial_statements_tags_income_statement": { + "template": "django_ledger/financial_statements/tags/income_statement.html", + "elements": { + "account-action-{{ account.uuid }}": "#account-action-{{ account.uuid }}", + "dropdown-menu-{{ acc.uuid }}": "#dropdown-menu-{{ acc.uuid }}" + } + }, + "django_ledger_includes_widget_bs": { + "template": "django_ledger/includes/widget_bs.html", + "elements": {} + }, + "django_ledger_includes_widget_ic": { + "template": "django_ledger/includes/widget_ic.html", + "elements": {} + }, + "django_ledger_includes_page_header": { + "template": "django_ledger/includes/page_header.html", + "elements": {} + }, + "django_ledger_includes_widget_ratios": { + "template": "django_ledger/includes/widget_ratios.html", + "elements": {} + }, + "django_ledger_includes_breadcrumbs": { + "template": "django_ledger/includes/breadcrumbs.html", + "elements": {} + }, + "django_ledger_includes_messages": { + "template": "django_ledger/includes/messages.html", + "elements": {} + }, + "django_ledger_includes_footer": { + "template": "django_ledger/includes/footer.html", + "elements": {} + }, + "django_ledger_includes_nav": { + "template": "django_ledger/includes/nav.html", + "elements": { + "djl-navbar-menu": "#djl-navbar-menu", + "djl-el=logout-button-nav": "#djl-el=logout-button-nav" + } + }, + "django_ledger_includes_card_markdown": { + "template": "django_ledger/includes/card_markdown.html", + "elements": {} + }, + "django_ledger_chart_of_accounts_coa_list": { + "template": "django_ledger/chart_of_accounts/coa_list.html", + "elements": {} + }, + "django_ledger_chart_of_accounts_coa_update": { + "template": "django_ledger/chart_of_accounts/coa_update.html", + "elements": { + "{{ form.form_id }}": "#{{ form.form_id }}" + } + }, + "django_ledger_chart_of_accounts_coa_create": { + "template": "django_ledger/chart_of_accounts/coa_create.html", + "elements": { + "{{ form.get_form_id }}": "#{{ form.get_form_id }}" + } + }, + "django_ledger_chart_of_accounts_includes_coa_card": { + "template": "django_ledger/chart_of_accounts/includes/coa_card.html", + "elements": {} + }, + "django_ledger_product_product_delete": { + "template": "django_ledger/product/product_delete.html", + "elements": {} + }, + "django_ledger_product_product_create": { + "template": "django_ledger/product/product_create.html", + "elements": {} + }, + "django_ledger_product_product_update": { + "template": "django_ledger/product/product_update.html", + "elements": {} + }, + "django_ledger_product_product_list": { + "template": "django_ledger/product/product_list.html", + "elements": {} + }, + "django_ledger_product_tags_product_table": { + "template": "django_ledger/product/tags/product_table.html", + "elements": { + "product-action-{{ invoice.uuid }}": "#product-action-{{ invoice.uuid }}", + "dropdown-menu-{{ product.uuid }}": "#dropdown-menu-{{ product.uuid }}" + } + }, + "django_ledger_purchase_order_po_detail": { + "template": "django_ledger/purchase_order/po_detail.html", + "elements": {} + }, + "django_ledger_purchase_order_po_delete": { + "template": "django_ledger/purchase_order/po_delete.html", + "elements": {} + }, + "django_ledger_purchase_order_po_create": { + "template": "django_ledger/purchase_order/po_create.html", + "elements": {} + }, + "django_ledger_purchase_order_po_list": { + "template": "django_ledger/purchase_order/po_list.html", + "elements": {} + }, + "django_ledger_purchase_order_po_update": { + "template": "django_ledger/purchase_order/po_update.html", + "elements": {} + }, + "django_ledger_purchase_order_includes_po_item_formset": { + "template": "django_ledger/purchase_order/includes/po_item_formset.html", + "elements": { + "{{ f.instance.html_id_unit_cost }}": "#{{ f.instance.html_id_unit_cost }}", + "{{ f.instance.html_id_quantity }}": "#{{ f.instance.html_id_quantity }}", + "{{ f.instance.html_id_total_amount }}": "#{{ f.instance.html_id_total_amount }}" + } + }, + "django_ledger_purchase_order_includes_po_table": { + "template": "django_ledger/purchase_order/includes/po_table.html", + "elements": { + "bill-action-{{ po.uuid }}": "#bill-action-{{ po.uuid }}", + "dropdown-menu-{{ po.uuid }}": "#dropdown-menu-{{ po.uuid }}" + } + }, + "django_ledger_purchase_order_includes_card_po": { + "template": "django_ledger/purchase_order/includes/card_po.html", + "elements": { + "{{ po_model.get_mark_as_review_html_id }}-button": "#{{ po_model.get_mark_as_review_html_id }}-button", + "{{ po_model.get_mark_as_approved_html_id }}-button": "#{{ po_model.get_mark_as_approved_html_id }}-button", + "{{ po_model.get_mark_as_fulfilled_html_id }}-button": "#{{ po_model.get_mark_as_fulfilled_html_id }}-button", + "{{ po_model.get_mark_as_delete_html_id }}-button": "#{{ po_model.get_mark_as_delete_html_id }}-button", + "{{ po_model.get_mark_as_void_html_id }}-button": "#{{ po_model.get_mark_as_void_html_id }}-button", + "{{ po_model.get_mark_as_canceled_html_id }}-button": "#{{ po_model.get_mark_as_canceled_html_id }}-button" + } + }, + "django_ledger_purchase_order_tags_po_item_table": { + "template": "django_ledger/purchase_order/tags/po_item_table.html", + "elements": {} + }, + "django_ledger_components_activity_form": { + "template": "django_ledger/components/activity_form.html", + "elements": {} + }, + "django_ledger_components_date_picker": { + "template": "django_ledger/components/date_picker.html", + "elements": { + "{{ date_picker_id }}": "#{{ date_picker_id }}" + } + }, + "django_ledger_components_filters": { + "template": "django_ledger/components/filters.html", + "elements": {} + }, + "django_ledger_components_breadcrumbs": { + "template": "django_ledger/components/breadcrumbs.html", + "elements": {} + }, + "django_ledger_components_feedback_button": { + "template": "django_ledger/components/feedback_button.html", + "elements": { + "{{ bug_modal_html_id }}": "#{{ bug_modal_html_id }}", + "{{ feature_modal_html_id }}": "#{{ feature_modal_html_id }}" + } + }, + "django_ledger_components_menu": { + "template": "django_ledger/components/menu.html", + "elements": {} + }, + "django_ledger_components_icon": { + "template": "django_ledger/components/icon.html", + "elements": {} + }, + "django_ledger_components_period_navigator": { + "template": "django_ledger/components/period_navigator.html", + "elements": {} + }, + "django_ledger_components_chart_container": { + "template": "django_ledger/components/chart_container.html", + "elements": { + "{{ chart_id }}": "#{{ chart_id }}" + } + }, + "django_ledger_components_modals": { + "template": "django_ledger/components/modals.html", + "elements": { + "{{ object.get_html_id }}": "#{{ object.get_html_id }}" + } + }, + "django_ledger_components_default_entity": { + "template": "django_ledger/components/default_entity.html", + "elements": { + "djetler-set-entity-form-{{ form_id }}": "#djetler-set-entity-form-{{ form_id }}" + } + }, + "django_ledger_components_modals_v2": { + "template": "django_ledger/components/modals_v2.html", + "elements": { + "{{ html_id }}": "#{{ html_id }}" + } + }, + "django_ledger_transactions_tags_txs_table": { + "template": "django_ledger/transactions/tags/txs_table.html", + "elements": {} + }, + "django_ledger_bills_bill_detail": { + "template": "django_ledger/bills/bill_detail.html", + "elements": { + "djl-bill-detail-amount-paid": "#djl-bill-detail-amount-paid", + "djl-bill-detail-amount-prepaid": "#djl-bill-detail-amount-prepaid", + "djl-bill-detail-amount-unearned": "#djl-bill-detail-amount-unearned", + "djl-bill-detail-amount-owed": "#djl-bill-detail-amount-owed" + } + }, + "django_ledger_bills_bill_delete": { + "template": "django_ledger/bills/bill_delete.html", + "elements": {} + }, + "django_ledger_bills_bill_update": { + "template": "django_ledger/bills/bill_update.html", + "elements": {} + }, + "django_ledger_bills_bill_void": { + "template": "django_ledger/bills/bill_void.html", + "elements": {} + }, + "django_ledger_bills_bill_create": { + "template": "django_ledger/bills/bill_create.html", + "elements": { + "djl-bill-model-create-form-id": "#djl-bill-model-create-form-id", + "djl-bill-create-button": "#djl-bill-create-button", + "djl-bill-create-back-button": "#djl-bill-create-back-button" + } + }, + "django_ledger_bills_bill_list": { + "template": "django_ledger/bills/bill_list.html", + "elements": {} + }, + "django_ledger_bills_includes_card_bill": { + "template": "django_ledger/bills/includes/card_bill.html", + "elements": { + "djl-bill-card-widget": "#djl-bill-card-widget", + "djl-bill-detail-update-button": "#djl-bill-detail-update-button", + "{{ bill.get_mark_as_review_html_id }}-button": "#{{ bill.get_mark_as_review_html_id }}-button", + "{{ bill.get_mark_as_approved_html_id }}-button": "#{{ bill.get_mark_as_approved_html_id }}-button", + "{{ bill.get_mark_as_paid_html_id }}-button": "#{{ bill.get_mark_as_paid_html_id }}-button", + "{{ bill.get_mark_as_void_html_id }}-button": "#{{ bill.get_mark_as_void_html_id }}-button", + "{{ bill.get_mark_as_cenceled_html_id }}-button": "#{{ bill.get_mark_as_cenceled_html_id }}-button" + } + }, + "django_ledger_bills_tags_bill_item_formset": { + "template": "django_ledger/bills/tags/bill_item_formset.html", + "elements": { + "{{ f.instance.html_id_quantity }}": "#{{ f.instance.html_id_quantity }}", + "{{ f.instance.html_id_unit_cost }}": "#{{ f.instance.html_id_unit_cost }}", + "{{ f.instance.html_id_total_amount }}": "#{{ f.instance.html_id_total_amount }}" + } + }, + "django_ledger_bills_tags_bill_table": { + "template": "django_ledger/bills/tags/bill_table.html", + "elements": { + "{{ bill.get_html_id }}": "#{{ bill.get_html_id }}", + "{{ bill.get_html_amount_due_id }}": "#{{ bill.get_html_amount_due_id }}", + "{{ bill.get_html_amount_paid_id }}": "#{{ bill.get_html_amount_paid_id }}", + "bill-action-{{ bill.uuid }}": "#bill-action-{{ bill.uuid }}", + "dropdown-menu-{{ bill.uuid }}": "#dropdown-menu-{{ bill.uuid }}" + } + }, + "django_ledger_layouts_content_layout_1": { + "template": "django_ledger/layouts/content_layout_1.html", + "elements": {} + }, + "django_ledger_layouts_base": { + "template": "django_ledger/layouts/base.html", + "elements": {} + }, + "django_ledger_layouts_content_layout_2": { + "template": "django_ledger/layouts/content_layout_2.html", + "elements": {} + }, + "django_ledger_bank_account_bank_account_create": { + "template": "django_ledger/bank_account/bank_account_create.html", + "elements": {} + }, + "django_ledger_bank_account_bank_account_update": { + "template": "django_ledger/bank_account/bank_account_update.html", + "elements": {} + }, + "django_ledger_bank_account_bank_account_list": { + "template": "django_ledger/bank_account/bank_account_list.html", + "elements": {} + }, + "django_ledger_bank_account_tags_bank_accounts_table": { + "template": "django_ledger/bank_account/tags/bank_accounts_table.html", + "elements": { + "bank-action-{{ bank_acc.uuid }}": "#bank-action-{{ bank_acc.uuid }}", + "dropdown-menu-{{ bank_acc.uuid }}": "#dropdown-menu-{{ bank_acc.uuid }}" + } + }, + "django_ledger_inventory_inventory_item_create": { + "template": "django_ledger/inventory/inventory_item_create.html", + "elements": {} + }, + "django_ledger_inventory_inventory_recount": { + "template": "django_ledger/inventory/inventory_recount.html", + "elements": {} + }, + "django_ledger_inventory_inventory_item_update": { + "template": "django_ledger/inventory/inventory_item_update.html", + "elements": {} + }, + "django_ledger_inventory_inventory_list": { + "template": "django_ledger/inventory/inventory_list.html", + "elements": {} + }, + "django_ledger_inventory_inventory_item_list": { + "template": "django_ledger/inventory/inventory_item_list.html", + "elements": {} + }, + "django_ledger_inventory_tags_inventory_item_table": { + "template": "django_ledger/inventory/tags/inventory_item_table.html", + "elements": { + "inv-item-action-{{ inv_item.uuid }}": "#inv-item-action-{{ inv_item.uuid }}", + "dropdown-menu-{{ inv_item.uuid }}": "#dropdown-menu-{{ inv_item.uuid }}" + } + }, + "django_ledger_inventory_tags_inventory_table": { + "template": "django_ledger/inventory/tags/inventory_table.html", + "elements": {} + }, + "django_ledger_data_import_data_import_job_list": { + "template": "django_ledger/data_import/data_import_job_list.html", + "elements": {} + }, + "django_ledger_data_import_import_job_update": { + "template": "django_ledger/data_import/import_job_update.html", + "elements": {} + }, + "django_ledger_data_import_import_job_create": { + "template": "django_ledger/data_import/import_job_create.html", + "elements": { + "file-js-django-ledger": "#file-js-django-ledger" + } + }, + "django_ledger_data_import_data_import_job_txs": { + "template": "django_ledger/data_import/data_import_job_txs.html", + "elements": {} + }, + "django_ledger_data_import_import_job_delete": { + "template": "django_ledger/data_import/import_job_delete.html", + "elements": {} + }, + "django_ledger_data_import_tags_data_import_job_txs_imported": { + "template": "django_ledger/data_import/tags/data_import_job_txs_imported.html", + "elements": {} + }, + "django_ledger_data_import_tags_data_import_job_txs_table": { + "template": "django_ledger/data_import/tags/data_import_job_txs_table.html", + "elements": {} + }, + "django_ledger_data_import_tags_data_import_job_list_table": { + "template": "django_ledger/data_import/tags/data_import_job_list_table.html", + "elements": { + "customer-action-{{ job.uuid }}": "#customer-action-{{ job.uuid }}", + "dropdown-menu-{{ customer.uuid }}": "#dropdown-menu-{{ customer.uuid }}" + } + }, + "django_ledger_estimate_estimate_update": { + "template": "django_ledger/estimate/estimate_update.html", + "elements": {} + }, + "django_ledger_estimate_estimate_create": { + "template": "django_ledger/estimate/estimate_create.html", + "elements": {} + }, + "django_ledger_estimate_estimate_list": { + "template": "django_ledger/estimate/estimate_list.html", + "elements": {} + }, + "django_ledger_estimate_estimate_detail": { + "template": "django_ledger/estimate/estimate_detail.html", + "elements": { + "djl-cj-detail-estimated-revenue": "#djl-cj-detail-estimated-revenue", + "djl-cj-detail-estimated-cost": "#djl-cj-detail-estimated-cost" + } + }, + "django_ledger_estimate_includes_estimate_table": { + "template": "django_ledger/estimate/includes/estimate_table.html", + "elements": { + "{{ ce_model.get_html_id }}": "#{{ ce_model.get_html_id }}", + "cj-action-{{ ce_model.uuid }}": "#cj-action-{{ ce_model.uuid }}", + "dropdown-menu-{{ ce_model.uuid }}": "#dropdown-menu-{{ ce_model.uuid }}" + } + }, + "django_ledger_estimate_includes_card_estimate": { + "template": "django_ledger/estimate/includes/card_estimate.html", + "elements": { + "{{ estimate_model.get_mark_as_draft_html_id }}-button": "#{{ estimate_model.get_mark_as_draft_html_id }}-button", + "{{ estimate_model.get_mark_as_review_html_id }}-button": "#{{ estimate_model.get_mark_as_review_html_id }}-button", + "{{ estimate_model.get_mark_as_approved_html_id }}-button": "#{{ estimate_model.get_mark_as_approved_html_id }}-button" + } + }, + "django_ledger_estimate_includes_estimate_item_table": { + "template": "django_ledger/estimate/includes/estimate_item_table.html", + "elements": {} + }, + "django_ledger_estimate_tags_ce_item_formset": { + "template": "django_ledger/estimate/tags/ce_item_formset.html", + "elements": { + "{{ f.instance.html_id_quantity }}": "#{{ f.instance.html_id_quantity }}", + "{{ f.instance.html_id_unit_cost }}": "#{{ f.instance.html_id_unit_cost }}", + "{{ f.instance.html_id_total_amount }}": "#{{ f.instance.html_id_total_amount }}" + } + }, + "django_ledger_account_account_list": { + "template": "django_ledger/account/account_list.html", + "elements": {} + }, + "django_ledger_account_account_create_child": { + "template": "django_ledger/account/account_create_child.html", + "elements": {} + }, + "django_ledger_account_account_detail": { + "template": "django_ledger/account/account_detail.html", + "elements": {} + }, + "django_ledger_account_account_update": { + "template": "django_ledger/account/account_update.html", + "elements": {} + }, + "django_ledger_account_account_create": { + "template": "django_ledger/account/account_create.html", + "elements": { + "{{ form.form_id }}": "#{{ form.form_id }}" + } + }, + "django_ledger_account_tags_accounts_table": { + "template": "django_ledger/account/tags/accounts_table.html", + "elements": { + "account-action-{{ account.uuid }}": "#account-action-{{ account.uuid }}", + "dropdown-menu-{{ account.uuid }}": "#dropdown-menu-{{ account.uuid }}" + } + }, + "django_ledger_account_tags_account_txs_table": { + "template": "django_ledger/account/tags/account_txs_table.html", + "elements": { + "tx-action-{{ tx.uuid }}": "#tx-action-{{ tx.uuid }}", + "dropdown-menu-{{ tx.uuid }}": "#dropdown-menu-{{ tx.uuid }}" + } + }, + "django_ledger_service_service_update": { + "template": "django_ledger/service/service_update.html", + "elements": {} + }, + "django_ledger_service_service_create": { + "template": "django_ledger/service/service_create.html", + "elements": {} + }, + "django_ledger_service_service_delete": { + "template": "django_ledger/service/service_delete.html", + "elements": {} + }, + "django_ledger_service_service_list": { + "template": "django_ledger/service/service_list.html", + "elements": {} + }, + "django_ledger_service_tags_services_table": { + "template": "django_ledger/service/tags/services_table.html", + "elements": { + "sercice-action-{{ invoice.uuid }}": "#sercice-action-{{ invoice.uuid }}", + "dropdown-menu-{{ service.uuid }}": "#dropdown-menu-{{ service.uuid }}" + } + }, + "django_ledger_journal_entry_je_create": { + "template": "django_ledger/journal_entry/je_create.html", + "elements": {} + }, + "django_ledger_journal_entry_je_list": { + "template": "django_ledger/journal_entry/je_list.html", + "elements": {} + }, + "django_ledger_journal_entry_je_update": { + "template": "django_ledger/journal_entry/je_update.html", + "elements": {} + }, + "django_ledger_journal_entry_je_detail": { + "template": "django_ledger/journal_entry/je_detail.html", + "elements": {} + }, + "django_ledger_journal_entry_je_detail_txs": { + "template": "django_ledger/journal_entry/je_detail_txs.html", + "elements": {} + }, + "django_ledger_journal_entry_je_delete": { + "template": "django_ledger/journal_entry/je_delete.html", + "elements": {} + }, + "django_ledger_journal_entry_includes_card_journal_entry": { + "template": "django_ledger/journal_entry/includes/card_journal_entry.html", + "elements": {} + }, + "django_ledger_journal_entry_tags_je_table": { + "template": "django_ledger/journal_entry/tags/je_table.html", + "elements": { + "je-action-{{ journal_entry_model.uuid }}": "#je-action-{{ journal_entry_model.uuid }}", + "dropdown-menu-{{ journal_entry_model.uuid }}": "#dropdown-menu-{{ journal_entry_model.uuid }}" + } + }, + "django_ledger_journal_entry_tags_je_txs_table": { + "template": "django_ledger/journal_entry/tags/je_txs_table.html", + "elements": {} + }, + "django_ledger_uom_uom_list": { + "template": "django_ledger/uom/uom_list.html", + "elements": {} + }, + "django_ledger_uom_uom_delete": { + "template": "django_ledger/uom/uom_delete.html", + "elements": {} + }, + "django_ledger_uom_uom_update": { + "template": "django_ledger/uom/uom_update.html", + "elements": {} + }, + "django_ledger_uom_uom_create": { + "template": "django_ledger/uom/uom_create.html", + "elements": {} + }, + "django_ledger_uom_tags_uom_table": { + "template": "django_ledger/uom/tags/uom_table.html", + "elements": { + "uom-action-{{ uom.uuid }}": "#uom-action-{{ uom.uuid }}", + "dropdown-menu-{{ invoice.uuid }}": "#dropdown-menu-{{ invoice.uuid }}" + } + }, + "django_ledger_customer_customer_update": { + "template": "django_ledger/customer/customer_update.html", + "elements": {} + }, + "django_ledger_customer_customer_list": { + "template": "django_ledger/customer/customer_list.html", + "elements": {} + }, + "django_ledger_customer_customer_create": { + "template": "django_ledger/customer/customer_create.html", + "elements": {} + }, + "django_ledger_customer_includes_card_customer": { + "template": "django_ledger/customer/includes/card_customer.html", + "elements": {} + }, + "django_ledger_customer_tags_customer_table": { + "template": "django_ledger/customer/tags/customer_table.html", + "elements": { + "customer-action-{{ customer.uuid }}": "#customer-action-{{ customer.uuid }}", + "dropdown-menu-{{ customer.uuid }}": "#dropdown-menu-{{ customer.uuid }}" + } + }, + "django_ledger_vendor_vendor_update": { + "template": "django_ledger/vendor/vendor_update.html", + "elements": {} + }, + "django_ledger_vendor_vendor_list": { + "template": "django_ledger/vendor/vendor_list.html", + "elements": {} + }, + "django_ledger_vendor_vendor_create": { + "template": "django_ledger/vendor/vendor_create.html", + "elements": {} + }, + "django_ledger_vendor_includes_card_vendor": { + "template": "django_ledger/vendor/includes/card_vendor.html", + "elements": { + "djl-vendor-card-widget": "#djl-vendor-card-widget" + } + }, + "django_ledger_vendor_tags_vendor_table": { + "template": "django_ledger/vendor/tags/vendor_table.html", + "elements": { + "vendor-action-{{ vendor.uuid }}": "#vendor-action-{{ vendor.uuid }}", + "dropdown-menu-{{ vendor.uuid }}": "#dropdown-menu-{{ vendor.uuid }}" + } + }, + "django_ledger_invoice_invoice_update": { + "template": "django_ledger/invoice/invoice_update.html", + "elements": {} + }, + "django_ledger_invoice_invoice_create": { + "template": "django_ledger/invoice/invoice_create.html", + "elements": { + "djl-bill-create-form-id": "#djl-bill-create-form-id", + "djl-invoice-create-button": "#djl-invoice-create-button", + "djl-invoice-create-back-button": "#djl-invoice-create-back-button" + } + }, + "django_ledger_invoice_invoice_list": { + "template": "django_ledger/invoice/invoice_list.html", + "elements": {} + }, + "django_ledger_invoice_invoice_detail": { + "template": "django_ledger/invoice/invoice_detail.html", + "elements": {} + }, + "django_ledger_invoice_invoice_delete": { + "template": "django_ledger/invoice/invoice_delete.html", + "elements": {} + }, + "django_ledger_invoice_includes_card_invoice": { + "template": "django_ledger/invoice/includes/card_invoice.html", + "elements": { + "{{ invoice.get_mark_as_review_html_id }}-button": "#{{ invoice.get_mark_as_review_html_id }}-button", + "{{ invoice.get_mark_as_approved_html_id }}-button": "#{{ invoice.get_mark_as_approved_html_id }}-button", + "{{ invoice.get_mark_as_paid_html_id }}-button": "#{{ invoice.get_mark_as_paid_html_id }}-button", + "{{ invoice.get_mark_as_void_html_id }}-button": "#{{ invoice.get_mark_as_void_html_id }}-button", + "{{ invoice.get_mark_as_canceled_html_id }}-button": "#{{ invoice.get_mark_as_canceled_html_id }}-button" + } + }, + "django_ledger_invoice_tags_invoice_item_formset": { + "template": "django_ledger/invoice/tags/invoice_item_formset.html", + "elements": { + "{{ f.instance.html_id_quantity }}": "#{{ f.instance.html_id_quantity }}", + "{{ f.instance.html_id_unit_cost }}": "#{{ f.instance.html_id_unit_cost }}", + "{{ f.instance.html_id_total_amount }}": "#{{ f.instance.html_id_total_amount }}" + } + }, + "django_ledger_invoice_tags_invoice_table": { + "template": "django_ledger/invoice/tags/invoice_table.html", + "elements": { + "invoice-action-{{ invoice.uuid }}": "#invoice-action-{{ invoice.uuid }}", + "dropdown-menu-{{ invoice.uuid }}": "#dropdown-menu-{{ invoice.uuid }}" + } + }, + "appointment_default_thank_you": { + "template": "appointment/default_thank_you.html", + "elements": {} + }, + "appointment_thank_you": { + "template": "appointment/thank_you.html", + "elements": {} + }, + "appointment_appointment_client_information": { + "template": "appointment/appointment_client_information.html", + "elements": { + "user-info": "#user-info", + "service-datetime-chosen": "#service-datetime-chosen" + } + }, + "appointment_appointments": { + "template": "appointment/appointments.html", + "elements": { + "calendar": "#calendar", + "slot-list": "#slot-list", + "reason_for_rescheduling": "#reason_for_rescheduling", + "staff_id": "#staff_id", + "service-datetime-chosen": "#service-datetime-chosen" + } + }, + "appointment_enter_verification_code": { + "template": "appointment/enter_verification_code.html", + "elements": { + "verification-code": "#verification-code" + } + }, + "appointment_rescheduling_thank_you": { + "template": "appointment/rescheduling_thank_you.html", + "elements": {} + }, + "appointment_set_password": { + "template": "appointment/set_password.html", + "elements": {} + }, + "base_templates_base": { + "template": "base_templates/base.html", + "elements": {} + }, + "administration_manage_staff_personal_info": { + "template": "administration/manage_staff_personal_info.html", + "elements": { + "updatePersonalInfoForm": "#updatePersonalInfoForm" + } + }, + "administration_manage_staff_member": { + "template": "administration/manage_staff_member.html", + "elements": {} + }, + "administration_display_appointment": { + "template": "administration/display_appointment.html", + "elements": {} + }, + "administration_user_profile": { + "template": "administration/user_profile.html", + "elements": {} + }, + "administration_manage_working_hours": { + "template": "administration/manage_working_hours.html", + "elements": { + "workingHoursForm": "#workingHoursForm", + "\n {% if working_hours_instance %}{{ working_hours_instance.id }}{% else %}0{% endif %}": "#\n {% if working_hours_instance %}{{ working_hours_instance.id }}{% else %}0{% endif %}", + "{% if staff_user_id %}{{ staff_user_id }}{% else %}0{% endif %}": "#{% if staff_user_id %}{{ staff_user_id }}{% else %}0{% endif %}", + "start-timepicker": "#start-timepicker", + "{{ working_hours_form.start_time.id_for_label }}": "#{{ working_hours_form.start_time.id_for_label }}", + "end-timepicker": "#end-timepicker", + "{{ working_hours_form.end_time.id_for_label }}": "#{{ working_hours_form.end_time.id_for_label }}", + "addWorkingHoursUrl": "#addWorkingHoursUrl", + "updateWorkingHoursUrl": "#updateWorkingHoursUrl" + } + }, + "administration_staff_list": { + "template": "administration/staff_list.html", + "elements": {} + }, + "administration_manage_service": { + "template": "administration/manage_service.html", + "elements": {} + }, + "administration_staff_index": { + "template": "administration/staff_index.html", + "elements": { + "calendar": "#calendar", + "event-list-container": "#event-list-container", + "customContextMenu": "#customContextMenu", + "newAppointmentOption": "#newAppointmentOption", + "clientName": "#clientName", + "clientEmail": "#clientEmail", + "emailError": "#emailError", + "clientPhone": "#clientPhone", + "clientAddress": "#clientAddress", + "want_reminder": "#want_reminder", + "additional_info": "#additional_info" + } + }, + "administration_manage_day_off": { + "template": "administration/manage_day_off.html", + "elements": { + "{{ day_off_form.start_date.id_for_label }}_display": "#{{ day_off_form.start_date.id_for_label }}_display", + "{{ day_off_form.start_date.id_for_label }}": "#{{ day_off_form.start_date.id_for_label }}", + "{{ day_off_form.end_date.id_for_label }}_display": "#{{ day_off_form.end_date.id_for_label }}_display", + "{{ day_off_form.end_date.id_for_label }}": "#{{ day_off_form.end_date.id_for_label }}", + "{{ day_off_form.description.id_for_label }}": "#{{ day_off_form.description.id_for_label }}" + } + }, + "administration_email_change_verification_code": { + "template": "administration/email_change_verification_code.html", + "elements": {} + }, + "administration_service_list": { + "template": "administration/service_list.html", + "elements": {} + }, + "error_pages_404_not_found": { + "template": "error_pages/404_not_found.html", + "elements": { + "svg2": "#svg2", + "metadata8": "#metadata8", + "defs6": "#defs6", + "Strips2_1": "#Strips2_1", + "rect5419": "#rect5419", + "linearGradient6096": "#linearGradient6096", + "stop6094": "#stop6094", + "layer1": "#layer1", + "g6219": "#g6219", + "path6180": "#path6180", + "g6174": "#g6174", + "path4488": "#path4488", + "path4490": "#path4490", + "path4496": "#path4496", + "rect4553": "#rect4553", + "path4513": "#path4513", + "path4517": "#path4517", + "path4521": "#path4521", + "path4525": "#path4525", + "path4533": "#path4533", + "path4537": "#path4537", + "path4541": "#path4541", + "path4545": "#path4545", + "path4549": "#path4549", + "path4556": "#path4556", + "path4560": "#path4560", + "path4529": "#path4529", + "path4614": "#path4614", + "path4616": "#path4616", + "path4565": "#path4565", + "path4567": "#path4567", + "path4570": "#path4570", + "path4578": "#path4578", + "path4578-1": "#path4578-1", + "path4610": "#path4610", + "path4573": "#path4573", + "path4575": "#path4575", + "path4579": "#path4579", + "layer3": "#layer3", + "text4526": "#text4526", + "path4555": "#path4555", + "text4526-2": "#text4526-2", + "path4558": "#path4558", + "errorText": "#errorText", + "back": "#back" + } + }, + "error_pages_304_already_submitted": { + "template": "error_pages/304_already_submitted.html", + "elements": { + "Layer_1": "#Layer_1" + } + }, + "error_pages_403_forbidden_rescheduling": { + "template": "error_pages/403_forbidden_rescheduling.html", + "elements": {} + }, + "error_pages_403_forbidden": { + "template": "error_pages/403_forbidden.html", + "elements": { + "back": "#back" + } + }, + "modal_event_details_modal": { + "template": "modal/event_details_modal.html", + "elements": { + "eventDetailsModal": "#eventDetailsModal", + "eventModalLabel": "#eventModalLabel", + "eventModalBody": "#eventModalBody", + "eventGoBtn": "#eventGoBtn", + "eventCancelBtn": "#eventCancelBtn", + "eventEditBtn": "#eventEditBtn", + "eventSubmitBtn": "#eventSubmitBtn", + "eventDeleteBtn": "#eventDeleteBtn" + } + }, + "modal_error_modal": { + "template": "modal/error_modal.html", + "elements": { + "errorModal": "#errorModal", + "errorModalLabel": "#errorModalLabel", + "errorModalMessage": "#errorModalMessage" + } + }, + "modal_confirm_modal": { + "template": "modal/confirm_modal.html", + "elements": { + "confirmModal": "#confirmModal", + "modalLabel": "#modalLabel", + "modalBody": "#modalBody", + "modalActionBtn": "#modalActionBtn" + } + }, + "email_sender_reminder_email": { + "template": "email_sender/reminder_email.html", + "elements": {} + }, + "email_sender_reschedule_email": { + "template": "email_sender/reschedule_email.html", + "elements": {} + }, + "email_sender_admin_new_appointment_email": { + "template": "email_sender/admin_new_appointment_email.html", + "elements": {} + }, + "email_sender_thank_you_email": { + "template": "email_sender/thank_you_email.html", + "elements": {} + }, + "ordered_model_admin_order_controls": { + "template": "ordered_model/admin/order_controls.html", + "elements": {} + }, + "plans_fake_payments": { + "template": "plans/fake_payments.html", + "elements": {} + }, + "plans_create_order": { + "template": "plans/create_order.html", + "elements": {} + }, + "plans_billing_info_create_or_update": { + "template": "plans/billing_info_create_or_update.html", + "elements": {} + }, + "plans_pagination": { + "template": "plans/pagination.html", + "elements": {} + }, + "plans_base": { + "template": "plans/base.html", + "elements": {} + }, + "plans_expiration_messages": { + "template": "plans/expiration_messages.html", + "elements": {} + }, + "plans_pricing": { + "template": "plans/pricing.html", + "elements": {} + }, + "plans_current": { + "template": "plans/current.html", + "elements": {} + }, + "plans_order_list": { + "template": "plans/order_list.html", + "elements": {} + }, + "plans_account_activation": { + "template": "plans/account_activation.html", + "elements": {} + }, + "plans_order_detail_table": { + "template": "plans/order_detail_table.html", + "elements": {} + }, + "plans_upgrade": { + "template": "plans/upgrade.html", + "elements": {} + }, + "plans_extend": { + "template": "plans/extend.html", + "elements": { + "pricing_form": "#pricing_form" + } + }, + "plans_billing_info_delete": { + "template": "plans/billing_info_delete.html", + "elements": {} + }, + "plans_plan_table": { + "template": "plans/plan_table.html", + "elements": {} + }, + "plans_order_detail": { + "template": "plans/order_detail.html", + "elements": { + "order_printable_documents": "#order_printable_documents" + } + }, + "plans_invoices_PL_EN": { + "template": "plans/invoices/PL_EN.html", + "elements": {} + }, + "plans_invoices_PL_EN_layout": { + "template": "plans/invoices/PL_EN_layout.html", + "elements": { + "full_number": "#full_number", + "shipping": "#shipping", + "items": "#items" + } + }, + "plans_invoices_invoice_base": { + "template": "plans/invoices/invoice_base.html", + "elements": {} + }, + "admin_import_export_change_list_export": { + "template": "admin/import_export/change_list_export.html", + "elements": {} + }, + "admin_import_export_base": { + "template": "admin/import_export/base.html", + "elements": {} + }, + "admin_import_export_change_list_import": { + "template": "admin/import_export/change_list_import.html", + "elements": {} + }, + "admin_import_export_change_list": { + "template": "admin/import_export/change_list.html", + "elements": {} + }, + "admin_import_export_change_list_export_item": { + "template": "admin/import_export/change_list_export_item.html", + "elements": {} + }, + "admin_import_export_change_list_import_export": { + "template": "admin/import_export/change_list_import_export.html", + "elements": {} + }, + "admin_import_export_import": { + "template": "admin/import_export/import.html", + "elements": {} + }, + "admin_import_export_resource_fields_list": { + "template": "admin/import_export/resource_fields_list.html", + "elements": {} + }, + "admin_import_export_change_list_import_item": { + "template": "admin/import_export/change_list_import_item.html", + "elements": {} + }, + "admin_import_export_change_form": { + "template": "admin/import_export/change_form.html", + "elements": {} + }, + "admin_import_export_export": { + "template": "admin/import_export/export.html", + "elements": {} + }, + "schema_graph_schema": { + "template": "schema_graph/schema.html", + "elements": { + "schema-graph-app": "#schema-graph-app" + } + }, + "django_filters_rest_framework_form": { + "template": "django_filters/rest_framework/form.html", + "elements": {} + }, + "django_filters_rest_framework_crispy_form": { + "template": "django_filters/rest_framework/crispy_form.html", + "elements": {} + }, + "django_filters_widgets_multiwidget": { + "template": "django_filters/widgets/multiwidget.html", + "elements": {} + } + }, + "common_elements": { + "navigation": { + "inventory": "#inventory-menu, .inventory-nav, nav .inventory", + "finance": "#finance-menu, .finance-nav, nav .finance", + "customers": "#customers-menu, .customers-nav, nav .customers" + }, + "actions": { + "add": ".btn-add, .add-button, button:contains('Add')", + "edit": ".btn-edit, .edit-button, button:contains('Edit')", + "save": ".btn-save, button[type='submit'], #save-button", + "cancel": ".btn-cancel, #cancel-button, button:contains('Cancel')", + "delete": ".btn-delete, .delete-button, button:contains('Delete')" + }, + "forms": { + "search": "#search-form, .search-input, input[name='q']", + "date_range": ".date-range, input[type='date']", + "dropdown": "select, .dropdown, .select-field" + } + } +} \ No newline at end of file diff --git a/templates/administration/display_appointment.html b/templates/administration/display_appointment.html index 954be742..c936b1a0 100644 --- a/templates/administration/display_appointment.html +++ b/templates/administration/display_appointment.html @@ -81,7 +81,8 @@
- + + {% trans 'Service price' %}: {{ appointment.get_appointment_amount_to_pay_text }}
diff --git a/templates/base.html b/templates/base.html index abc030c3..053f18b6 100644 --- a/templates/base.html +++ b/templates/base.html @@ -40,6 +40,7 @@ + {% if LANGUAGE_CODE == 'ar' %} @@ -76,6 +77,7 @@ {% endblock period_navigation %} {% block content %} + {% endblock content%} {% block body %} {% endblock body%} @@ -96,6 +98,11 @@ + + + + + @@ -115,6 +122,7 @@ +{% endblock %} \ No newline at end of file diff --git a/templates/tours/tour_list.html b/templates/tours/tour_list.html new file mode 100644 index 00000000..386882bf --- /dev/null +++ b/templates/tours/tour_list.html @@ -0,0 +1,32 @@ +{% extends "base.html" %} + +{% block title %}Interactive Guides{% endblock %} + +{% block content %} +
+

Interactive Guides

+

Learn how to use the car inventory system with these interactive step-by-step guides.

+ +
+ {% for tour in tours %} +
+
+
+
{{ tour.name }}
+

{{ tour.description }}

+
+ +
+
+ {% empty %} +
+
+ No interactive guides available at this time. +
+
+ {% endfor %} +
+
+{% endblock %} diff --git a/tours/__init__.py b/tours/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tours/admin.py b/tours/admin.py new file mode 100644 index 00000000..0bdb93a8 --- /dev/null +++ b/tours/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from . import models + +# Register your models here. +admin.site.register(models.Tour) +admin.site.register(models.TourCompletion) \ No newline at end of file diff --git a/tours/apps.py b/tours/apps.py new file mode 100644 index 00000000..57f8071a --- /dev/null +++ b/tours/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ToursConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'tours' diff --git a/tours/management/__init__.py b/tours/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tours/management/commands/__init__.py b/tours/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tours/management/commands/generate_tours.py b/tours/management/commands/generate_tours.py new file mode 100644 index 00000000..b7fa82e7 --- /dev/null +++ b/tours/management/commands/generate_tours.py @@ -0,0 +1,84 @@ +from django.core.management.base import BaseCommand +import yaml +import os +import json +from django.conf import settings + + +class Command(BaseCommand): + help = "Generate IntroJS tour definitions from workflow documentation" + + def handle(self, *args, **kwargs): + input_file = "haikal_kb.yaml" + output_dir = os.path.join(settings.BASE_DIR, 'static', 'js', 'tours') + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + try: + with open(input_file, 'r', encoding='utf-8') as f: + kb = yaml.safe_load(f) + except Exception as e: + self.stdout.write(self.style.ERROR(f"Error reading knowledge base file: {e}")) + return + + workflows = kb.get("user_workflows", {}) + + tours_created = 0 + + # Map of common UI elements to their likely selectors + element_selectors = { + "inventory": "#inventory-menu, .inventory-nav, nav .inventory", + "add car": "#add-car-button, .btn-add-car, button:contains('Add Car')", + "save": "button[type='submit'], .btn-save, #save-button", + "cancel": ".btn-cancel, #cancel-button, button:contains('Cancel')", + "vin": "#vin-input, input[name='vin'], .vin-field", + "make": "#make-select, select[name='make'], .make-field", + "model": "#model-select, select[name='model'], .model-field", + "series": "#series-select, select[name='series'], .series-field", + "trim": "#trim-select, select[name='trim'], .trim-field", + "price": "#price-input, input[name='price'], .price-field", + "color": "#color-select, select[name='color'], .color-field", + "invoice": "#invoice-section, .invoice-tab, #create-invoice", + "customer": "#customer-select, select[name='customer'], .customer-field", + "finance": "#finance-menu, .finance-nav, nav .finance", + } + + for workflow_name, workflow in workflows.items(): + steps = workflow.get("steps", []) + if not steps: + continue + + tour_steps = [] + + for i, step in enumerate(steps): + # Try to identify UI element from step description + element = None + for key, selector in element_selectors.items(): + if key.lower() in step.lower(): + element = selector + break + + tour_step = { + "title": f"Step {i + 1}", + "intro": step, + "position": "bottom" + } + + if element: + tour_step["element"] = element + + tour_steps.append(tour_step) + + # Save the tour definition as JSON + tour_filename = workflow_name.lower().replace(' ', '_') + '_tour.json' + with open(os.path.join(output_dir, tour_filename), 'w', encoding='utf-8') as f: + json.dump({ + "name": workflow_name, + "description": workflow.get("description", ""), + "steps": tour_steps + }, f, indent=2) + + tours_created += 1 + + self.stdout.write(self.style.SUCCESS(f"✅ Created {tours_created} IntroJS tour definitions in {output_dir}")) diff --git a/tours/management/commands/generate_ui_map.py b/tours/management/commands/generate_ui_map.py new file mode 100644 index 00000000..30258ec3 --- /dev/null +++ b/tours/management/commands/generate_ui_map.py @@ -0,0 +1,88 @@ +from django.core.management.base import BaseCommand +from django.urls import get_resolver +import os +import json +from django.template.loaders.app_directories import get_app_template_dirs + + +class Command(BaseCommand): + help = "Generate UI element map for IntroJS tours" + + def handle(self, *args, **kwargs): + output_file = os.path.join('static', 'js', 'tours', 'ui_element_map.json') + + ui_map = { + "pages": {}, + "common_elements": { + "navigation": { + "inventory": "#inventory-menu, .inventory-nav, nav .inventory", + "finance": "#finance-menu, .finance-nav, nav .finance", + "customers": "#customers-menu, .customers-nav, nav .customers" + }, + "actions": { + "add": ".btn-add, .add-button, button:contains('Add')", + "edit": ".btn-edit, .edit-button, button:contains('Edit')", + "save": ".btn-save, button[type='submit'], #save-button", + "cancel": ".btn-cancel, #cancel-button, button:contains('Cancel')", + "delete": ".btn-delete, .delete-button, button:contains('Delete')" + }, + "forms": { + "search": "#search-form, .search-input, input[name='q']", + "date_range": ".date-range, input[type='date']", + "dropdown": "select, .dropdown, .select-field" + } + } + } + + # Extract URL patterns to identify pages + resolver = get_resolver() + for url_pattern in resolver.url_patterns: + if hasattr(url_pattern, 'name') and url_pattern.name: + pattern_name = url_pattern.name + # Skip admin and API URLs + if pattern_name.startswith(('admin:', 'api:')): + continue + + ui_map["pages"][pattern_name] = { + "url_pattern": str(url_pattern.pattern), + "elements": {} + } + + # Scan templates for UI elements with IDs + template_dirs = get_app_template_dirs('templates') + for template_dir in template_dirs: + for root, dirs, files in os.walk(template_dir): + for file in files: + if not file.endswith(('.html', '.htm')): + continue + + try: + with open(os.path.join(root, file), 'r', encoding='utf-8') as f: + content = f.read() + + # Try to identify the page/view this template is for + template_path = os.path.relpath(os.path.join(root, file), template_dir) + page_key = template_path.replace('/', '_').replace('.html', '') + + # Create page entry if it doesn't exist + if page_key not in ui_map["pages"]: + ui_map["pages"][page_key] = { + "template": template_path, + "elements": {} + } + + # Extract elements with IDs + import re + id_matches = re.findall(r'id=["\']([^"\']+)["\']', content) + for id_match in id_matches: + ui_map["pages"][page_key]["elements"][id_match] = f"#{id_match}" + + except Exception as e: + self.stdout.write(self.style.WARNING(f"Error processing template {file}: {e}")) + + # Save UI map as JSON + os.makedirs(os.path.dirname(output_file), exist_ok=True) + with open(output_file, 'w', encoding='utf-8') as f: + json.dump(ui_map, f, indent=2) + + self.stdout.write(self.style.SUCCESS(f"✅ UI element map saved to {output_file}")) diff --git a/tours/management/commands/import_tours.py b/tours/management/commands/import_tours.py new file mode 100644 index 00000000..16474a30 --- /dev/null +++ b/tours/management/commands/import_tours.py @@ -0,0 +1,45 @@ +from django.core.management.base import BaseCommand +import yaml +import os +from django.utils.text import slugify +from tours.models import Tour + + +class Command(BaseCommand): + help = "Import tours from knowledge base" + + def handle(self, *args, **kwargs): + input_file = "haikal_kb.yaml" + + try: + with open(input_file, 'r', encoding='utf-8') as f: + kb = yaml.safe_load(f) + except Exception as e: + self.stdout.write(self.style.ERROR(f"Error reading knowledge base file: {e}")) + return + + workflows = kb.get("user_workflows", {}) + + tours_created = 0 + + for workflow_name, workflow in workflows.items(): + slug = slugify(workflow_name) + tour_file = f"{slug}_tour.json" + + tour, created = Tour.objects.update_or_create( + slug=slug, + defaults={ + 'name': workflow_name, + 'description': workflow.get('description', ''), + 'tour_file': tour_file, + 'is_active': True + } + ) + + if created: + tours_created += 1 + self.stdout.write(self.style.SUCCESS(f"Created tour: {workflow_name}")) + else: + self.stdout.write(self.style.SUCCESS(f"Updated tour: {workflow_name}")) + + self.stdout.write(self.style.SUCCESS(f"✅ Imported {tours_created} tours from knowledge base")) diff --git a/tours/migrations/0001_initial.py b/tours/migrations/0001_initial.py new file mode 100644 index 00000000..d0939f6e --- /dev/null +++ b/tours/migrations/0001_initial.py @@ -0,0 +1,40 @@ +# Generated by Django 5.2.1 on 2025-06-06 14:05 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Tour', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('description', models.TextField(blank=True)), + ('slug', models.SlugField(unique=True)), + ('tour_file', models.CharField(max_length=255)), + ('is_active', models.BooleanField(default=True)), + ], + ), + migrations.CreateModel( + name='TourCompletion', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('completed_on', models.DateTimeField(auto_now_add=True)), + ('tour', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tours.tour')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'unique_together': {('tour', 'user')}, + }, + ), + ] diff --git a/tours/migrations/__init__.py b/tours/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tours/models.py b/tours/models.py new file mode 100644 index 00000000..5179072f --- /dev/null +++ b/tours/models.py @@ -0,0 +1,22 @@ +from django.db import models + + +class Tour(models.Model): + name = models.CharField(max_length=100) + description = models.TextField(blank=True) + slug = models.SlugField(unique=True) + tour_file = models.CharField(max_length=255) + is_active = models.BooleanField(default=True) + + def __str__(self): + return self.name + + +class TourCompletion(models.Model): + tour = models.ForeignKey(Tour, on_delete=models.CASCADE) + user = models.ForeignKey('auth.User', on_delete=models.CASCADE) + completed_on = models.DateTimeField(auto_now_add=True) + + class Meta: + unique_together = ('tour', 'user') + diff --git a/tours/tests.py b/tours/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/tours/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/tours/urls.py b/tours/urls.py new file mode 100644 index 00000000..ffa32531 --- /dev/null +++ b/tours/urls.py @@ -0,0 +1,9 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('', views.tour_list, name='tour_list'), + path('data//', views.get_tour_data, name='get_tour_data'), + path('complete//', views.mark_tour_completed, name='mark_tour_complete'), + path('start//', views.start_tour_view, name='start_tour'), +] diff --git a/tours/views.py b/tours/views.py new file mode 100644 index 00000000..06a6839d --- /dev/null +++ b/tours/views.py @@ -0,0 +1,55 @@ +import os +import json +from django.shortcuts import render, get_object_or_404 +from django.http import JsonResponse +from django.contrib.auth.decorators import login_required +from django.conf import settings +from .models import Tour, TourCompletion + + +@login_required +def tour_list(request): + tours = Tour.objects.filter(is_active=True) + return render(request, 'tours/tour_list.html', {'tours': tours}) + + +@login_required +def get_tour_data(request, slug): + tour = get_object_or_404(Tour, slug=slug, is_active=True) + + # Check if user has already completed this tour + completed = TourCompletion.objects.filter(tour=tour, user=request.user).exists() + + # Load the tour data from JSON file + tour_file_path = os.path.join(settings.BASE_DIR, 'static', 'js', 'tours', tour.tour_file) + + try: + with open(tour_file_path, 'r') as f: + tour_data = json.load(f) + except (FileNotFoundError, json.JSONDecodeError): + return JsonResponse({'error': 'Tour data not found or invalid'}, status=404) + + return JsonResponse({ + 'tour': tour_data, + 'completed': completed + }) + + +@login_required +def mark_tour_completed(request, slug): + if request.method != 'POST': + return JsonResponse({'error': 'Method not allowed'}, status=405) + + tour = get_object_or_404(Tour, slug=slug, is_active=True) + + # Mark the tour as completed for this user + TourCompletion.objects.get_or_create(tour=tour, user=request.user) + + return JsonResponse({'status': 'success'}) + + +@login_required +def start_tour_view(request, slug): + tour = get_object_or_404(Tour, slug=slug, is_active=True) + # Redirect to the page where the tour should start + return render(request, 'tours/start_tour.html', {'tour': tour})