diff --git a/dev_requirements.txt b/dev_requirements.txt new file mode 100644 index 0000000..b1d7367 --- /dev/null +++ b/dev_requirements.txt @@ -0,0 +1,3 @@ +coverage +django_nose +mock diff --git a/setup.py b/setup.py index f906146..5b73b89 100644 --- a/setup.py +++ b/setup.py @@ -37,5 +37,5 @@ include_package_data = True, install_requires = ['django>=1.4', 'django-social-auth>=0.7.8', - 'mailmanclient', ] + 'mailmanclient'] ) diff --git a/src/postorius/auth/decorators.py b/src/postorius/auth/decorators.py index 2b0c2c6..bded4aa 100644 --- a/src/postorius/auth/decorators.py +++ b/src/postorius/auth/decorators.py @@ -51,14 +51,14 @@ """ def wrapper(*args, **kwargs): user = args[0].user - fqdn_listname = kwargs['fqdn_listname'] + list_id = kwargs['list_id'] if not user.is_authenticated(): raise PermissionDenied if user.is_superuser: return fn(*args, **kwargs) if getattr(user, 'is_list_owner', None): return fn(*args, **kwargs) - mlist = List.objects.get_or_404(fqdn_listname=fqdn_listname) + mlist = List.objects.get_or_404(fqdn_listname=list_id) if user.email not in mlist.owners: raise PermissionDenied else: @@ -69,12 +69,12 @@ def list_moderator_required(fn): """Check if the logged in user is a moderator of the given list. - Assumes that the request object is the first arg and that fqdn_listname + Assumes that the request object is the first arg and that list_id is present in kwargs. """ def wrapper(*args, **kwargs): user = args[0].user - fqdn_listname = kwargs['fqdn_listname'] + list_id = kwargs['list_id'] if not user.is_authenticated(): raise PermissionDenied if user.is_superuser: @@ -83,7 +83,7 @@ return fn(*args, **kwargs) if getattr(user, 'is_list_moderator', None): return fn(*args, **kwargs) - mlist = List.objects.get_or_404(fqdn_listname=fqdn_listname) + mlist = List.objects.get_or_404(fqdn_listname=list_id) if user.email not in mlist.moderators and \ user.email not in mlist.owners: raise PermissionDenied diff --git a/src/postorius/templates/postorius/lists/confirm_delete.html b/src/postorius/templates/postorius/lists/confirm_delete.html index f03d794..d868e29 100644 --- a/src/postorius/templates/postorius/lists/confirm_delete.html +++ b/src/postorius/templates/postorius/lists/confirm_delete.html @@ -10,6 +10,6 @@

{% trans "All settings and membership data will be lost!" %}

{% csrf_token %} - {% trans "Cancel" %} + {% trans "Cancel" %}
{% endblock main %} diff --git a/src/postorius/templates/postorius/lists/confirm_remove_role.html b/src/postorius/templates/postorius/lists/confirm_remove_role.html index 4380f85..b7e1ccb 100644 --- a/src/postorius/templates/postorius/lists/confirm_remove_role.html +++ b/src/postorius/templates/postorius/lists/confirm_remove_role.html @@ -6,9 +6,9 @@ {% block main %}

{% trans 'Confirm Remove Role' %}

{% trans "Are you sure you want to remove?" %}

-
+ {% csrf_token %} - {% trans "Cancel" %} + {% trans "Cancel" %}
{% endblock main %} diff --git a/src/postorius/templates/postorius/lists/index.html b/src/postorius/templates/postorius/lists/index.html index a962f44..052ce37 100644 --- a/src/postorius/templates/postorius/lists/index.html +++ b/src/postorius/templates/postorius/lists/index.html @@ -29,7 +29,7 @@ {% for list in lists %} - {{ list.display_name }} + {{ list.display_name }} {{ list.fqdn_listname }} {{ list.settings.description }} diff --git a/src/postorius/templates/postorius/lists/settings.html b/src/postorius/templates/postorius/lists/settings.html index d51210b..a410776 100644 --- a/src/postorius/templates/postorius/lists/settings.html +++ b/src/postorius/templates/postorius/lists/settings.html @@ -8,14 +8,14 @@ {% list_nav 'list_settings' 'Settings' %} {% if visible_section %} - {% csrf_token %} + {% csrf_token %} {% for field in form %}
{{ field.errors }} diff --git a/src/postorius/templates/postorius/lists/summary.html b/src/postorius/templates/postorius/lists/summary.html index d6bf0ec..f6b0fec 100644 --- a/src/postorius/templates/postorius/lists/summary.html +++ b/src/postorius/templates/postorius/lists/summary.html @@ -17,10 +17,10 @@ {% if user.is_authenticated %} {% if userSubscribed %} - {% trans "Unsubscribe" %} + {% trans "Unsubscribe" %} {% else %}

{% trans 'Subscribe to this list' %}

- {% csrf_token %} + {% csrf_token %} {{subscribe_form.as_p}} diff --git a/src/postorius/templates/postorius/menu/list_nav.html b/src/postorius/templates/postorius/menu/list_nav.html index 453da60..1af4c90 100644 --- a/src/postorius/templates/postorius/menu/list_nav.html +++ b/src/postorius/templates/postorius/menu/list_nav.html @@ -5,21 +5,21 @@ {% trans 'Mailing Lists' %} » {{ list.fqdn_listname }} » {% trans title %} {% if user|lower != 'anonymoususer' %} {% endif %} diff --git a/src/postorius/tests/test_auth_decorators.py b/src/postorius/tests/test_auth_decorators.py index d979fb4..c5ba62b 100644 --- a/src/postorius/tests/test_auth_decorators.py +++ b/src/postorius/tests/test_auth_decorators.py @@ -30,12 +30,12 @@ @list_owner_required -def dummy_function(request, fqdn_listname): +def dummy_function(request, list_id): return True @list_moderator_required -def dummy_function_mod_req(request, fqdn_listname): +def dummy_function_mod_req(request, list_id): return True @@ -47,7 +47,7 @@ from postorius.tests.utils import create_mock_list self.request_factory = RequestFactory() # create a mock list with members - list_name = 'foolist@example.org' + list_name = 'foolist.example.org' list_id = 'foolist.example.org' self.mock_list = create_mock_list(dict( fqdn_listname=list_name, @@ -57,22 +57,22 @@ def test_not_authenticated(self, mock_get_list): """Should raise PermissionDenied if user is not authenticated.""" mock_get_list.return_value = self.mock_list - request = self.request_factory.get('/lists/foolist@example.org/' + request = self.request_factory.get('/lists/foolist.example.org/' 'settings/') request.user = AnonymousUser() self.assertRaises(PermissionDenied, dummy_function, request, - fqdn_listname='foolist@example.org') + list_id='foolist.example.org') @patch.object(Client, 'get_list') def test_superuser(self, mock_get_list): """Should call the dummy method, if user is superuser.""" mock_get_list.return_value = self.mock_list - request = self.request_factory.get('/lists/foolist@example.org/' + request = self.request_factory.get('/lists/foolist.example.org/' 'settings/') request.user = User.objects.create_superuser('su1', 'su@sodo.org', 'pwd') return_value = dummy_function(request, - fqdn_listname='foolist@example.org') + list_id='foolist.example.org') self.assertEqual(return_value, True) @patch.object(Client, 'get_list') @@ -82,12 +82,12 @@ self.mock_list.owners = ['geddy@rush.it'] mock_get_list.return_value = self.mock_list # prepare request - request = self.request_factory.get('/lists/foolist@example.org/' + request = self.request_factory.get('/lists/foolist.example.org/' 'settings/') request.user = User.objects.create_user('les c', 'les@primus.org', 'pwd') self.assertRaises(PermissionDenied, dummy_function, request, - fqdn_listname='foolist@example.org') + list_id='foolist.example.org') @patch.object(Client, 'get_list') def test_list_owner(self, mock_get_list): @@ -96,12 +96,12 @@ self.mock_list.owners = ['les@primus.org'] mock_get_list.return_value = self.mock_list # prepare request - request = self.request_factory.get('/lists/foolist@example.org/' + request = self.request_factory.get('/lists/foolist.example.org/' 'settings/') request.user = User.objects.create_user('les cl', 'les@primus.org', 'pwd') return_value = dummy_function(request, - fqdn_listname='foolist@example.org') + list_id='foolist.example.org') self.assertEqual(return_value, True) @@ -113,7 +113,7 @@ from postorius.tests.utils import create_mock_list self.request_factory = RequestFactory() # create a mock list with members - list_name = 'foolist@example.org' + list_name = 'foolist.example.org' list_id = 'foolist.example.org' self.mock_list = create_mock_list(dict( fqdn_listname=list_name, @@ -123,23 +123,23 @@ def test_not_authenticated(self, mock_get_list): """Should raise PermissionDenied if user is not authenticated.""" mock_get_list.return_value = self.mock_list - request = self.request_factory.get('/lists/foolist@example.org/' + request = self.request_factory.get('/lists/foolist.example.org/' 'settings/') request.user = AnonymousUser() self.assertRaises(PermissionDenied, dummy_function_mod_req, request, - fqdn_listname='foolist@example.org') + list_id='foolist.example.org') @patch.object(Client, 'get_list') def test_superuser(self, mock_get_list): """Should call the dummy method, if user is superuser.""" mock_get_list.return_value = self.mock_list - request = self.request_factory.get('/lists/foolist@example.org/' + request = self.request_factory.get('/lists/foolist.example.org/' 'settings/') request.user = User.objects.create_superuser('su2', 'su@sodo.org', 'pwd') return_value = dummy_function_mod_req(request, - fqdn_listname= - 'foolist@example.org') + list_id= + 'foolist.example.org') self.assertEqual(return_value, True) @patch.object(Client, 'get_list') @@ -149,12 +149,12 @@ self.mock_list.moderators = ['geddy@rush.it'] mock_get_list.return_value = self.mock_list # prepare request - request = self.request_factory.get('/lists/foolist@example.org/' + request = self.request_factory.get('/lists/foolist.example.org/' 'settings/') request.user = User.objects.create_user('les cl2', 'les@primus.org', 'pwd') self.assertRaises(PermissionDenied, dummy_function_mod_req, request, - fqdn_listname='foolist@example.org') + list_id='foolist.example.org') @patch.object(Client, 'get_list') def test_list_owner(self, mock_get_list): @@ -163,13 +163,13 @@ self.mock_list.owners = ['les@primus.org'] mock_get_list.return_value = self.mock_list # prepare request - request = self.request_factory.get('/lists/foolist@example.org/' + request = self.request_factory.get('/lists/foolist.example.org/' 'settings/') request.user = User.objects.create_user('les cl3', 'les@primus.org', 'pwd') return_value = dummy_function_mod_req(request, - fqdn_listname= - 'foolist@example.org') + list_id= + 'foolist.example.org') self.assertEqual(return_value, True) @patch.object(Client, 'get_list') @@ -179,11 +179,11 @@ self.mock_list.moderators = ['les@primus.org'] mock_get_list.return_value = self.mock_list # prepare request - request = self.request_factory.get('/lists/foolist@example.org/' + request = self.request_factory.get('/lists/foolist.example.org/' 'settings/') request.user = User.objects.create_user('les cl4', 'les@primus.org', 'pwd') return_value = dummy_function_mod_req(request, - fqdn_listname= - 'foolist@example.org') + list_id= + 'foolist.example.org') self.assertEqual(return_value, True) diff --git a/src/postorius/urls.py b/src/postorius/urls.py index 2ccd47f..1205998 100644 --- a/src/postorius/urls.py +++ b/src/postorius/urls.py @@ -108,7 +108,7 @@ url(r'^lists/new/$', 'list_new', name='list_new'), url(r'^more_info/(?P[^/]+)/(?P[^/]+)$', 'more_info_tab', name='more_info_tab'), - url(r'^lists/(?P[^/]+)/', include(per_list_urlpatterns)), + url(r'^lists/(?P[^/]+)/', include(per_list_urlpatterns)), url(r'^users/address_activation/$', AddressActivationView.as_view(), name='address_activation'), diff --git a/src/postorius/views/generic.py b/src/postorius/views/generic.py index f491662..578d33f 100644 --- a/src/postorius/views/generic.py +++ b/src/postorius/views/generic.py @@ -45,8 +45,8 @@ Sets self.mailing_list to list object if fqdn_listname in **kwargs. """ - def _get_list(self, fqdn_listname, page): - return List.objects.get_or_404(fqdn_listname=fqdn_listname) + def _get_list(self, list_id, page): + return List.objects.get_or_404(fqdn_listname=list_id) def _is_list_owner(self, user, mailing_list): if not user.is_authenticated(): @@ -64,9 +64,9 @@ def dispatch(self, request, *args, **kwargs): # get the list object. - if 'fqdn_listname' in kwargs: + if 'list_id' in kwargs: try: - self.mailing_list = self._get_list(kwargs['fqdn_listname'], + self.mailing_list = self._get_list(kwargs['list_id'], int(kwargs.get('page', 1))) except MailmanApiError: return utils.render_api_error(request) diff --git a/src/postorius/views/list.py b/src/postorius/views/list.py index f2969c7..a590707 100644 --- a/src/postorius/views/list.py +++ b/src/postorius/views/list.py @@ -44,8 +44,8 @@ """Display all members of a given list. """ - def _get_list(self, fqdn_listname, page): - m_list = super(ListMembersView, self)._get_list(fqdn_listname, page) + def _get_list(self, list_id, page): + m_list = super(ListMembersView, self)._get_list(list_id, page) m_list.member_page = m_list.get_member_page(25, page) m_list.member_page_nr = page m_list.member_page_previous_nr = page - 1 @@ -54,7 +54,7 @@ return m_list @method_decorator(list_owner_required) - def post(self, request, fqdn_listname, page=1): + def post(self, request, list_id, page=1): if 'owner_email' in request.POST: owner_form = NewOwnerForm(request.POST) if owner_form.is_valid(): @@ -88,7 +88,7 @@ context_instance=RequestContext(request)) @method_decorator(list_owner_required) - def get(self, request, fqdn_listname, page=1): + def get(self, request, list_id, page=1): owner_form = NewOwnerForm() moderator_form = NewModeratorForm() return render_to_response('postorius/lists/members.html', @@ -102,11 +102,11 @@ '''View the preferences for a single member of a mailing list''' @method_decorator(list_owner_required) - def post(self, request, fqdn_listname, email): + def post(self, request, list_id, email): try: client = utils.get_client() - mm_member = client.get_member(fqdn_listname, email) - mm_list = client.get_list(fqdn_listname) + mm_member = client.get_member(list_id, email) + mm_list = client.get_list(list_id) preferences_form = UserPreferences(request.POST) if preferences_form.is_valid(): preferences = mm_member.preferences @@ -134,11 +134,11 @@ context_instance=RequestContext(request)) @method_decorator(list_owner_required) - def get(self, request, fqdn_listname, email): + def get(self, request, list_id, email): try: client = utils.get_client() - mm_member = client.get_member(fqdn_listname, email) - mm_list = client.get_list(fqdn_listname) + mm_member = client.get_member(list_id, email) + mm_list = client.get_list(list_id) settingsform = UserPreferences(initial=mm_member.preferences) except MailmanApiError: return utils.render_api_error(request) @@ -162,7 +162,7 @@ """ @method_decorator(list_owner_required) - def get(self, request, fqdn_listname): + def get(self, request, list_id): return render_to_response('postorius/lists/metrics.html', {'list': self.mailing_list}, context_instance=RequestContext(request)) @@ -173,7 +173,7 @@ """Shows common list metrics. """ - def get(self, request, fqdn_listname): + def get(self, request, list_id): user_email = getattr(request.user, 'email', None) userSubscribed = False try: @@ -195,7 +195,7 @@ """Subscribe a mailing list.""" @method_decorator(login_required) - def post(self, request, fqdn_listname): + def post(self, request, list_id): try: form = ListSubscribe(request.POST) if form.is_valid(): @@ -211,7 +211,7 @@ return utils.render_api_error(request) except HTTPError, e: messages.error(request, e.msg) - return redirect('list_summary', self.mailing_list.fqdn_listname) + return redirect('list_summary', self.mailing_list.list_id) class ListUnsubscribeView(MailingListView): @@ -230,7 +230,7 @@ return utils.render_api_error(request) except ValueError, e: messages.error(request, e) - return redirect('list_summary', self.mailing_list.fqdn_listname) + return redirect('list_summary', self.mailing_list.list_id) class ListMassSubsribeView(MailingListView): @@ -267,7 +267,7 @@ return utils.render_api_error(request) except HTTPError, e: messages.error(request, e) - return redirect('mass_subscribe', self.mailing_list.fqdn_listname) + return redirect('mass_subscribe', self.mailing_list.list_id) def _get_choosable_domains(request): @@ -313,7 +313,7 @@ list_settings.save() messages.success(request, _("List created")) return redirect("list_summary", - fqdn_listname=mailing_list.fqdn_listname) + list_id=mailing_list.list_id) # TODO catch correct Error class: except HTTPError, e: return render_to_response( @@ -344,7 +344,7 @@ return utils.render_api_error(request) choosable_domains = _get_choosable_domains(request) if request.method == 'POST': - return redirect("list_summary", fqdn_listname=request.POST["list"]) + return redirect("list_summary", list_id=request.POST["list"]) else: return render_to_response(template, {'error': error, @@ -354,7 +354,7 @@ @login_required -def list_subscriptions(request, option=None, fqdn_listname=None, +def list_subscriptions(request, option=None, list_id=None, user_email=None, template='postorius/lists/subscriptions.html', *args, **kwargs): @@ -369,11 +369,11 @@ error = None form_subscribe = None form_unsubscribe = None - if request.POST.get('fqdn_listname', ''): - fqdn_listname = request.POST.get('fqdn_listname', '') + if request.POST.get('list_id', ''): + list_id = request.POST.get('list_id', '') # connect REST and catch issues getting the list try: - the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) + the_list = List.objects.get_or_404(fqdn_listname=list_id) except AttributeError, e: return render_to_response('postorius/errors/generic.html', {'error': 'Mailman REST API not available.' @@ -444,7 +444,7 @@ initial={'fqdn_listname': fqdn_listname, 'email': request.user.username, 'name': 'unsubscribe'}) - the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) + the_list = List.objects.get_or_404(fqdn_listname=list_id) return render_to_response(template, {'form_subscribe': form_subscribe, 'form_unsubscribe': form_unsubscribe, @@ -455,11 +455,11 @@ @list_owner_required -def list_delete(request, fqdn_listname): +def list_delete(request, list_id): """Deletes a list but asks for confirmation first. """ try: - the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) + the_list = List.objects.get_or_404(fqdn_listname=list_id) except MailmanApiError: return utils.render_api_error(request) if request.method == 'POST': @@ -467,7 +467,7 @@ return redirect("list_index") else: submit_url = reverse('list_delete', - kwargs={'fqdn_listname': fqdn_listname}) + kwargs={'list_id': list_id}) cancel_url = reverse('list_index',) return render_to_response( 'postorius/lists/confirm_delete.html', @@ -477,11 +477,11 @@ @list_owner_required -def list_held_messages(request, fqdn_listname): +def list_held_messages(request, list_id): """Shows a list of held messages. """ try: - the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) + the_list = List.objects.get_or_404(fqdn_listname=list_id) except MailmanApiError: return utils.render_api_error(request) return render_to_response('postorius/lists/held_messages.html', @@ -490,71 +490,71 @@ @list_owner_required -def accept_held_message(request, fqdn_listname, msg_id): +def accept_held_message(request, list_id, msg_id): """Accepts a held message. """ try: - the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) + the_list = List.objects.get_or_404(fqdn_listname=list_id) the_list.accept_message(msg_id) except MailmanApiError: return utils.render_api_error(request) except HTTPError, e: messages.error(request, e.msg) - return redirect('list_held_messages', the_list.fqdn_listname) + return redirect('list_held_messages', the_list.list_id) messages.success(request, 'The message has been accepted.') - return redirect('list_held_messages', the_list.fqdn_listname) + return redirect('list_held_messages', the_list.list_id) @list_owner_required -def discard_held_message(request, fqdn_listname, msg_id): +def discard_held_message(request, list_id, msg_id): """Accepts a held message. """ try: - the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) + the_list = List.objects.get_or_404(fqdn_listname=list_id) the_list.discard_message(msg_id) except MailmanApiError: return utils.render_api_error(request) except HTTPError, e: messages.error(request, e.msg) - return redirect('list_held_messages', the_list.fqdn_listname) + return redirect('list_held_messages', the_list.list_id) messages.success(request, 'The message has been discarded.') - return redirect('list_held_messages', the_list.fqdn_listname) + return redirect('list_held_messages', the_list.list_id) @list_owner_required -def defer_held_message(request, fqdn_listname, msg_id): +def defer_held_message(request, list_id, msg_id): """Accepts a held message. """ try: - the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) + the_list = List.objects.get_or_404(fqdn_listname=list_id) the_list.defer_message(msg_id) except MailmanApiError: return utils.render_api_error(request) except HTTPError, e: messages.error(request, e.msg) - return redirect('list_held_messages', the_list.fqdn_listname) + return redirect('list_held_messages', the_list.list_id) messages.success(request, 'The message has been defered.') - return redirect('list_held_messages', the_list.fqdn_listname) + return redirect('list_held_messages', the_list.list_id) @list_owner_required -def reject_held_message(request, fqdn_listname, msg_id): +def reject_held_message(request, list_id, msg_id): """Accepts a held message. """ try: - the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) + the_list = List.objects.get_or_404(fqdn_listname=list_id) the_list.reject_message(msg_id) except MailmanApiError: return utils.render_api_error(request) except HTTPError, e: messages.error(request, e.msg) - return redirect('list_held_messages', the_list.fqdn_listname) + return redirect('list_held_messages', the_list.list_id) messages.success(request, 'The message has been rejected.') - return redirect('list_held_messages', the_list.fqdn_listname) + return redirect('list_held_messages', the_list.list_id) @list_owner_required -def list_settings(request, fqdn_listname=None, visible_section=None, +def list_settings(request, list_id=None, visible_section=None, visible_option=None, template='postorius/lists/settings.html'): """ @@ -572,7 +572,7 @@ visible_section = 'List Identity' form_sections = [] try: - the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) + the_list = List.objects.get_or_404(fqdn_listname=list_id) except MailmanApiError: return utils.render_api_error(request) # collect all Form sections for the links: @@ -623,12 +623,12 @@ @user_passes_test(lambda u: u.is_superuser) -def remove_role(request, fqdn_listname=None, role=None, address=None, +def remove_role(request, list_id=None, role=None, address=None, template='postorius/lists/confirm_remove_role.html'): """Removes a list moderator or owner. """ try: - the_list = List.objects.get_or_404(fqdn_listname=fqdn_listname) + the_list = List.objects.get_or_404(fqdn_listname=list_id) except MailmanApiError: return utils.render_api_error(request) @@ -636,12 +636,12 @@ if address not in the_list.owners: messages.error(request, _('The user {} is not an owner'.format(address))) - return redirect("list_members", the_list.fqdn_listname) + return redirect("list_members", the_list.list_id) elif role == 'moderator': if address not in the_list.moderators: messages.error(request, _('The user {} is not a moderator'.format(address))) - return redirect("list_members", the_list.fqdn_listname) + return redirect("list_members", the_list.list_id) if request.method == 'POST': try: @@ -651,13 +651,13 @@ except HTTPError as e: messages.error(request, _('The {0} could not be removed:' ' {1}'.format(role, e.msg))) - return redirect("list_members", the_list.fqdn_listname) + return redirect("list_members", the_list.list_id) messages.success(request, _('The user {0} has been removed as {1}.' .format(address, role))) - return redirect("list_members", the_list.fqdn_listname) + return redirect("list_members", the_list.list_id) return render_to_response(template, {'role': role, 'address': address, - 'fqdn_listname': the_list.fqdn_listname}, + 'list_id': the_list.list_id}, context_instance=RequestContext(request)) diff --git a/src/postorius/views/settings.py b/src/postorius/views/settings.py index 3859aec..fce005b 100644 --- a/src/postorius/views/settings.py +++ b/src/postorius/views/settings.py @@ -42,7 +42,6 @@ MailmanApiError, Mailman404Error) from postorius.forms import * from postorius.auth.decorators import * -from postorius.views.generic import MailingListView, MailmanUserView @login_required