diff --git a/tg_event_guest/README.rst b/tg_event_guest/README.rst new file mode 100644 index 0000000..a02ac5b --- /dev/null +++ b/tg_event_guest/README.rst @@ -0,0 +1,94 @@ +============ +Event guests +============ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:fc7a7eef77a1909fe77e3bb8cd041eedadb7eb2b7ee189bf08b1e902481bc4ea + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-it--projects--llc%2Ftg--addons-lightgray.png?logo=github + :target: https://github.com/it-projects-llc/tg-addons/tree/17.0/tg_event_guest + :alt: it-projects-llc/tg-addons + +|badge1| |badge2| |badge3| + +Module for inviting guests to event + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +Preparing guest +--------------- + +- Go to main menu -> Events -> Guests +- Create record with following mandatory fields set: + + - Name + - Email + - Event + - Event ticket + +- Save +- Run Actions -> Send mail +- In "Send mail" wizard leave everything default +- Send +- RESULT: email with invitation link is sent +- RESULT: invitation link is the same as in guest form + +Guest registering +----------------- + +- Copy "Invitation link" from event guest form or from invitiation + email as actual guest +- Open that link in browser as logged out user (probably in + Incognito/Private mode) +- Register and set password +- RESULT: you will be redirected to contact details portal page +- Fill in all required fields and press "Confirm" +- RESULT: new registration created. Event and event ticket are used + guest form, that is filled in "Preparing guest" section + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* IT-Projects LLC + +Contributors +------------ + +- Eugene Molotov (https://github.com/em230418) + +Maintainers +----------- + +This module is part of the `it-projects-llc/tg-addons `_ project on GitHub. + +You are welcome to contribute. diff --git a/tg_event_guest/__init__.py b/tg_event_guest/__init__.py new file mode 100644 index 0000000..48d9904 --- /dev/null +++ b/tg_event_guest/__init__.py @@ -0,0 +1,3 @@ +from . import models +from . import controllers +from . import wizard diff --git a/tg_event_guest/__manifest__.py b/tg_event_guest/__manifest__.py new file mode 100644 index 0000000..c279b99 --- /dev/null +++ b/tg_event_guest/__manifest__.py @@ -0,0 +1,19 @@ +{ + "name": """Event guests""", + "version": "17.0.0.2.0", + "author": "IT-Projects LLC", + "support": "it@it-projects.info", + "website": "https://github.com/it-projects-llc/tg-addons", + "license": "LGPL-3", + "depends": [ + "auth_signup", + "event_sale", + "portal", + ], + "data": [ + "security/ir.model.access.csv", + "data/mail_template_data.xml", + "views/event_guest_views.xml", + "views/portal_templates.xml", + ], +} diff --git a/tg_event_guest/controllers/__init__.py b/tg_event_guest/controllers/__init__.py new file mode 100644 index 0000000..5e73599 --- /dev/null +++ b/tg_event_guest/controllers/__init__.py @@ -0,0 +1,2 @@ +from . import portal +from . import main diff --git a/tg_event_guest/controllers/main.py b/tg_event_guest/controllers/main.py new file mode 100644 index 0000000..5173aa7 --- /dev/null +++ b/tg_event_guest/controllers/main.py @@ -0,0 +1,34 @@ +from odoo import _ +from odoo.http import request + +from odoo.addons.auth_signup.controllers.main import ( + AuthSignupHome as BaseAuthSignupHome, +) + + +class AuthSignupHome(BaseAuthSignupHome): + def get_auth_signup_qcontext(self): + qcontext = super().get_auth_signup_qcontext() + if not qcontext.get("guest_register_code") and request.params.get( + "guest_register_code" + ): + qcontext["guest_register_code"] = request.params.get("guest_register_code") + + guest_register_code = qcontext.get("guest_register_code") + if guest_register_code: + guest = request.env["event.guest"]._get_by_code(guest_register_code) + if guest: + if not qcontext.get("name"): + qcontext["name"] = guest.name or "" + if not qcontext.get("login"): + qcontext["login"] = guest.email or "" + if request.session.uid: + qcontext["error"] = _("You need to logout to register guest") + + return qcontext + + def _prepare_signup_values(self, qcontext): + values = super()._prepare_signup_values(qcontext) + if qcontext.get("guest_register_code"): + values["guest_register_code"] = qcontext["guest_register_code"] + return values diff --git a/tg_event_guest/controllers/portal.py b/tg_event_guest/controllers/portal.py new file mode 100644 index 0000000..6f7b199 --- /dev/null +++ b/tg_event_guest/controllers/portal.py @@ -0,0 +1,63 @@ +from odoo.http import request, route + +from odoo.addons.portal.controllers.portal import CustomerPortal as BaseCustomerPortal + + +class CustomerPortal(BaseCustomerPortal): + @route() + def home(self, **kw): + guest = request.env.user.event_guest + if guest and not guest.result_attendee: + return request.redirect("/my/account") + return super().home(**kw) + + @route() + def account(self, redirect=None, **post): + if not request.httprequest.method == "POST": + if post.get("guest_register_code"): + guest = request.env["event.guest"]._get_by_code( + post["guest_register_code"] + ) + if guest.guest_partner == request.env.user.partner_id: + guest.result_partner = request.env.user.partner_id + + res = super().account(redirect, **post) + + if not request.httprequest.method == "POST": + # we are handling only POST requests here + return res + + if res.status_code == 200: + # some fields are not correct + return res + + partner = request.env.user.partner_id + guest = request.env.user.event_guest + + if not guest: + return res + + if not guest.result_attendee: + Attendee = request.env["event.registration"].sudo() + vals = { + "name": partner.name, + "event_id": guest.event.id, + "event_ticket_id": guest.event_ticket.id, + "partner_id": partner.id, + } + + # additional field from partner_event + if "attendee_partner_id" in Attendee._fields: + vals["attendee_partner_id"] = partner.id + + attendee = Attendee.create(vals) + attendee.action_confirm() + + ctx = attendee.action_send_badge_email()["context"] + compose = ( + attendee.env["mail.compose.message"].with_context(**ctx).create({}) + ) + compose.action_send_mail() + guest.result_attendee = attendee + + return res diff --git a/tg_event_guest/data/mail_template_data.xml b/tg_event_guest/data/mail_template_data.xml new file mode 100644 index 0000000..0c185a7 --- /dev/null +++ b/tg_event_guest/data/mail_template_data.xml @@ -0,0 +1,29 @@ + + + + Guest Mass Mail + + {{ object.email }} + Invitation to {{ object.event.name }} + + +

Hello, object name!

+
+

+ Please complete your registration to event display_name using the + link +

+

Thank you!

+
+

You are receiving this email because invited_by name has invited you to event display_name. Please, let us know if you believe this email has been sent to you by mistake.

+
+

Tribal Gathering Crew

+
+
+
diff --git a/tg_event_guest/models/__init__.py b/tg_event_guest/models/__init__.py new file mode 100644 index 0000000..e1d822b --- /dev/null +++ b/tg_event_guest/models/__init__.py @@ -0,0 +1,2 @@ +from . import event_guest +from . import res_users diff --git a/tg_event_guest/models/event_guest.py b/tg_event_guest/models/event_guest.py new file mode 100644 index 0000000..432c0a1 --- /dev/null +++ b/tg_event_guest/models/event_guest.py @@ -0,0 +1,92 @@ +from random import choice as random_choice +from string import ascii_lowercase, digits +from urllib.parse import urljoin + +from odoo import api, fields, models + + +class EventGuest(models.Model): + _name = "event.guest" + _inherit = "mail.thread" + _description = "Event Guest" + _order = "id DESC" + + @api.model + def _default_code(self): + return "".join([random_choice(ascii_lowercase + digits) for i in range(8)]) + + name = fields.Char(required=True) + email = fields.Char(required=True) + event = fields.Many2one( + "event.event", required=True, domain="[('stage_id.pipe_end', '=', False)]" + ) + guest_partner = fields.Many2one( + "res.partner", compute="_compute_guest_partner", store=True + ) + + event_ticket = fields.Many2one( + "event.event.ticket", + required=True, + domain="[('event_id', '=', event), ('price', '=', 0)]", + ) + code = fields.Char( + index=True, required=True, default=lambda self: self._default_code() + ) + + invite_url = fields.Char("Invite URL", compute="_compute_invite_url", store=False) + invited_by = fields.Many2one("res.partner", readonly=True) + guest_of = fields.Many2one("res.partner") + + result_partner = fields.Many2one( + "res.partner", string="Related partner", readonly=True + ) + result_attendee = fields.Many2one( + "event.registration", string="Related attendee", readonly=True + ) + + _sql_constraints = [ + ( + "code_unique", + "unique(code)", + "Code must be unique", + ), + ] + + @api.model + def _get_by_code(self, code): + return self.sudo().search( + [ + ("code", "=", code), + ], + limit=1, + ) + + @api.depends("email") + def _compute_guest_partner(self): + with_email = self.filtered("email") + for record in self: + record.guest_partner = self.env["res.partner"].search( + [ + ("email", "=", record.email), + ], + limit=1, + ) + + (self - with_email).write( + { + "guest_partner": False, + } + ) + + @api.depends("code", "event", "guest_partner") + def _compute_invite_url(self): + for guest in self: + if guest.event: + if guest.guest_partner: + path = f"/my/account?guest_register_code={guest.code}" + else: + path = f"/web/signup?guest_register_code={guest.code}&redirect=%2Fmy%2Faccount" # noqa: B950 E501 + + guest.invite_url = urljoin(guest.event.get_base_url(), path) + else: + guest.invite_url = False diff --git a/tg_event_guest/models/res_users.py b/tg_event_guest/models/res_users.py new file mode 100644 index 0000000..c408b95 --- /dev/null +++ b/tg_event_guest/models/res_users.py @@ -0,0 +1,34 @@ +from odoo import api, fields, models + + +class ResUsers(models.Model): + _inherit = "res.users" + + event_guest = fields.Many2one("event.guest", compute="_compute_event_guest") + + def _compute_event_guest(self): + event_guests = self.env["event.guest"].search( + [ + ("result_partner", "in", self.mapped("partner_id").ids), + ] + ) + d = {x.result_partner.id: x.id for x in event_guests} + for user in self: + user.event_guest = d.get(user.partner_id.id, False) + + @api.model + def signup(self, values, token=None): + guest_register_code = values.pop("guest_register_code", False) + res = super().signup(values, token) + if guest_register_code: + guest = self.env["event.guest"]._get_by_code(guest_register_code) + if guest and not guest.result_partner: + user = self.search( + [ + ("login", "=", res[0]), + ], + limit=1, + ) + guest.result_partner = user.partner_id + + return res diff --git a/tg_event_guest/pyproject.toml b/tg_event_guest/pyproject.toml new file mode 100644 index 0000000..4231d0c --- /dev/null +++ b/tg_event_guest/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/tg_event_guest/readme/CONTRIBUTORS.md b/tg_event_guest/readme/CONTRIBUTORS.md new file mode 100644 index 0000000..2f4c137 --- /dev/null +++ b/tg_event_guest/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Eugene Molotov (https://github.com/em230418) diff --git a/tg_event_guest/readme/DESCRIPTION.md b/tg_event_guest/readme/DESCRIPTION.md new file mode 100644 index 0000000..5658503 --- /dev/null +++ b/tg_event_guest/readme/DESCRIPTION.md @@ -0,0 +1 @@ +Module for inviting guests to event diff --git a/tg_event_guest/readme/USAGE.md b/tg_event_guest/readme/USAGE.md new file mode 100644 index 0000000..3582881 --- /dev/null +++ b/tg_event_guest/readme/USAGE.md @@ -0,0 +1,25 @@ +Preparing guest +--------------- + +- Go to main menu -> Events -> Guests +- Create record with following mandatory fields set: + * Name + * Email + * Event + * Event ticket +- Save +- Run Actions -> Send mail +- In "Send mail" wizard leave everything default +- Send +- RESULT: email with invitation link is sent +- RESULT: invitation link is the same as in guest form + +Guest registering +----------------- + +- Copy "Invitation link" from event guest form or from invitiation email as actual guest +- Open that link in browser as logged out user (probably in Incognito/Private mode) +- Register and set password +- RESULT: you will be redirected to contact details portal page +- Fill in all required fields and press "Confirm" +- RESULT: new registration created. Event and event ticket are used guest form, that is filled in "Preparing guest" section diff --git a/tg_event_guest/security/ir.model.access.csv b/tg_event_guest/security/ir.model.access.csv new file mode 100644 index 0000000..fdedb1b --- /dev/null +++ b/tg_event_guest/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_event_guest_user,access_event_guest_user,model_event_guest,base.group_user,1,1,1,1 +access_event_guest_public,access_event_guest_public,model_event_guest,,1,0,0,0 diff --git a/tg_event_guest/static/description/index.html b/tg_event_guest/static/description/index.html new file mode 100644 index 0000000..aa8cf70 --- /dev/null +++ b/tg_event_guest/static/description/index.html @@ -0,0 +1,457 @@ + + + + + +Event guests + + + +
+

Event guests

+ + +

Beta License: LGPL-3 it-projects-llc/tg-addons

+

Module for inviting guests to event

+

Table of contents

+ +
+

Usage

+
+

Preparing guest

+
    +
  • Go to main menu -> Events -> Guests
  • +
  • Create record with following mandatory fields set:
      +
    • Name
    • +
    • Email
    • +
    • Event
    • +
    • Event ticket
    • +
    +
  • +
  • Save
  • +
  • Run Actions -> Send mail
  • +
  • In “Send mail” wizard leave everything default
  • +
  • Send
  • +
  • RESULT: email with invitation link is sent
  • +
  • RESULT: invitation link is the same as in guest form
  • +
+
+
+

Guest registering

+
    +
  • Copy “Invitation link” from event guest form or from invitiation +email as actual guest
  • +
  • Open that link in browser as logged out user (probably in +Incognito/Private mode)
  • +
  • Register and set password
  • +
  • RESULT: you will be redirected to contact details portal page
  • +
  • Fill in all required fields and press “Confirm”
  • +
  • RESULT: new registration created. Event and event ticket are used +guest form, that is filled in “Preparing guest” section
  • +
+
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • IT-Projects LLC
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is part of the it-projects-llc/tg-addons project on GitHub.

+

You are welcome to contribute.

+
+
+
+ + diff --git a/tg_event_guest/tests/__init__.py b/tg_event_guest/tests/__init__.py new file mode 100644 index 0000000..6c9812d --- /dev/null +++ b/tg_event_guest/tests/__init__.py @@ -0,0 +1 @@ +from . import test_main diff --git a/tg_event_guest/tests/test_main.py b/tg_event_guest/tests/test_main.py new file mode 100644 index 0000000..d1c0637 --- /dev/null +++ b/tg_event_guest/tests/test_main.py @@ -0,0 +1,79 @@ +from datetime import datetime, timedelta + +from odoo import fields +from odoo.tests import TransactionCase + + +class TestMain(TransactionCase): + def test_signup_01(self): + self.env["res.config.settings"].create( + { + "auth_signup_uninvited": "b2c", + } + ).execute() + + Users = self.env["res.users"].with_context( + mail_create_nolog=True, + mail_create_nosubscribe=True, + mail_notrack=True, + no_reset_password=True, + ) + + event = self.env["event.event"].create( + { + "name": "Test event", + "date_begin": fields.Datetime.to_string( + datetime.today() + timedelta(days=1) + ), + "date_end": fields.Datetime.to_string( + datetime.today() + timedelta(days=15) + ), + "event_ticket_ids": [ + ( + 0, + 0, + { + "name": "Test ticket", + }, + ) + ], + } + ) + + guest = self.env["event.guest"].create( + { + "name": "Eugene", + "email": "eugene@mailforspam.com", + "event": event.id, + "event_ticket": event.event_ticket_ids[0].id, + } + ) + + guest_user_tuple = Users.signup( + { + "name": "eugene1", + "login": "eugene1", + "password": "eugene1", + "guest_register_code": guest.code, + } + ) + guest_user = Users.search([("login", "=", guest_user_tuple[1])]) + + guest = self.env["event.guest"].browse(guest.id) + self.assertEqual(guest.result_partner, guest_user.partner_id) + + # for example, some reason other test user registered with already used guest code # noqa: E501 + + guest_user_tuple = Users.signup( + { + "name": "eugene2", + "login": "eugene2", + "password": "eugene2", + "guest_register_code": guest.code, + } + ) + accident_guest_user = Users.search([("login", "=", guest_user_tuple[1])]) + + guest = self.env["event.guest"].browse(guest.id) + self.assertNotEqual(guest.result_partner, accident_guest_user.partner_id) + self.assertEqual(guest.result_partner, guest_user.partner_id) diff --git a/tg_event_guest/views/event_guest_views.xml b/tg_event_guest/views/event_guest_views.xml new file mode 100644 index 0000000..b9078a3 --- /dev/null +++ b/tg_event_guest/views/event_guest_views.xml @@ -0,0 +1,103 @@ + + + + event.guest.form + event.guest + +
+ + + + + + + + + + + + + + + + + +
+ + +
+
+
+
+ + + event.guest.tree + event.guest + + + + + + + + + + + + + + + + event.guest.search + event.guest + + + + + + + + + + + + Guests + event.guest + tree,form + { + 'search_default_no_result_attendee': 1 + } + + + + + + Send email + mail.compose.message + form + new + + + list,form + + +
diff --git a/tg_event_guest/views/portal_templates.xml b/tg_event_guest/views/portal_templates.xml new file mode 100644 index 0000000..fd2e9e5 --- /dev/null +++ b/tg_event_guest/views/portal_templates.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/tg_event_guest/wizard/__init__.py b/tg_event_guest/wizard/__init__.py new file mode 100644 index 0000000..b528d99 --- /dev/null +++ b/tg_event_guest/wizard/__init__.py @@ -0,0 +1 @@ +from . import mail_compose_message diff --git a/tg_event_guest/wizard/mail_compose_message.py b/tg_event_guest/wizard/mail_compose_message.py new file mode 100644 index 0000000..8458403 --- /dev/null +++ b/tg_event_guest/wizard/mail_compose_message.py @@ -0,0 +1,20 @@ +from odoo import api, models + + +class MailComposeMessage(models.TransientModel): + _inherit = "mail.compose.message" + + def _action_send_mail(self, *args, **kw): + for wizard in self.filtered(lambda w: w.model == "event.guest"): + active_ids = self.env.context.get("active_ids") or [wizard.res_id] + self.env[wizard.model].browse( + active_ids + ).invited_by = self.env.user.partner_id + return super()._action_send_mail(*args, **kw) + + @api.model + def default_get(self, fields): + res = super().default_get(fields) + if res.get("model") == "event.guest" and res.get("email_from"): + res["reply_to"] = res["email_from"] + return res